From f0ab9cdd21caab4917391808a8e7d73a2b0fbe87 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 31 May 2024 20:29:27 +0200 Subject: [PATCH 01/92] remove ... hint from symbols period key --- app/src/main/assets/layouts/functional_keys.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/layouts/functional_keys.json b/app/src/main/assets/layouts/functional_keys.json index de7890f44..858309014 100644 --- a/app/src/main/assets/layouts/functional_keys.json +++ b/app/src/main/assets/layouts/functional_keys.json @@ -15,7 +15,7 @@ { "label": "emoji" }, { "label": "numpad" }, { "label": "space" }, - { "label": "period" }, + { "label": "period", "labelFlags": 1073741824 }, { "label": "action", "width": 0.15 } ] ] From c048ff6ff60d8bdeacf5e3ccba4d182af7c9dfd9 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 1 Jun 2024 13:50:06 +0200 Subject: [PATCH 02/92] update tools scripts --- .gitignore | 1 + tools/diacritics.py | 91 +++++++++++++++++++ tools/make-dict-list/README.md | 5 - tools/make-dict-list/build.gradle | 18 ---- .../main/kotlin/tools/dict/MakeDictList.kt | 63 ------------- release.py => tools/release.py | 66 ++++++++++++-- 6 files changed, 149 insertions(+), 95 deletions(-) create mode 100644 tools/diacritics.py delete mode 100644 tools/make-dict-list/README.md delete mode 100644 tools/make-dict-list/build.gradle delete mode 100644 tools/make-dict-list/src/main/kotlin/tools/dict/MakeDictList.kt rename release.py => tools/release.py (52%) diff --git a/.gitignore b/.gitignore index 99f9ce5b7..f7b396404 100755 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ app/build app/release app/.cxx fastlane/Appfile +tools/*.txt diff --git a/tools/diacritics.py b/tools/diacritics.py new file mode 100644 index 000000000..5b92f737d --- /dev/null +++ b/tools/diacritics.py @@ -0,0 +1,91 @@ +#!/bin/python + +import sys +import os +import re + + +file_ending_filter = "-words.txt" +word_lists_dir = "../../wordlists/" + + +def find_word_lists(language: str) -> list[str]: + # return a list of files + files = list() + if not os.path.isdir(word_lists_dir + language): + return files + for (dirpath, dirnames, filenames) in os.walk(word_lists_dir + language): + for n in filenames: + if n.endswith(file_ending_filter): + files.append(dirpath + "/" + n) + return files + + +def check_diacritics(language: str, diacritics: list[str], all_diacritics: set[str]): + word_lists = find_word_lists(language) + if len(word_lists) == 0: + return + for dia in diacritics: + all_diacritics.remove(dia) + foreign_dia = "".join(all_diacritics) + dia_regex = fr"[{foreign_dia}]" + print("checking", language, "with", diacritics) + foreigns = list() + dia_count = dict() + for dia in diacritics: + dia_count[dia] = 0 + for word_list in word_lists: + with open(word_list) as f: + # check whether file contains any diacritics that are not in the list + for line in f: + if re.search(dia_regex, line): + foreigns.append(line.rstrip()) + else: + # search for language diacritics and add a count + for dia in diacritics: + if dia in line: + try: + # assuming the format from https://www.wortschatz.uni-leipzig.de/en/download + count = int(line.split("\t")[2]) + except: + count = 1 + dia_count[dia] = dia_count[dia] + count + dia_results = f"language: {language}\n" + dia_results = dia_results + f"diacritics: {diacritics}\n" + dia_results = dia_results + f"language diacritics counts: {dia_count}\n" + dia_results = dia_results + "foreign diacritics:\n" + dia_results = dia_results + "\n".join(foreigns) + with open(f"diacritics_report_{language}.txt", 'w') as f: + f.write(dia_results) + + +def make_all_diacritics(dia_lists: list[list[str]]) -> set[str]: + all_dia = set() + for dia_list in dia_lists: + for dia in dia_list: + all_dia.add(dia) + return all_dia + + +def read_diacritics() -> dict[str, list[str]]: + d = dict() + language = "" + with open("diacritics.txt") as f: + for line in f: + if language == "": + language = line.strip() + else: + d[language] = list(map(str.strip, line.split(","))) + language = "" + return d + + +def main(): + diacritics = read_diacritics() + all_diacritics = make_all_diacritics(list(diacritics.values())) + for key in diacritics: + check_diacritics(key, diacritics[key], all_diacritics.copy()) + + +if __name__ == "__main__": + main() diff --git a/tools/make-dict-list/README.md b/tools/make-dict-list/README.md deleted file mode 100644 index 8927e592c..000000000 --- a/tools/make-dict-list/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# make-dict-list - -This module takes care of generating a list of dictionaries available in the [dictionaries repository](https://codeberg.org/Helium314/aosp-dictionaries) for convenient linking when adding dictionaries in HeliBoard. - -To use it, simply run `./gradlew tools:make-dict-list:makeDictList` diff --git a/tools/make-dict-list/build.gradle b/tools/make-dict-list/build.gradle deleted file mode 100644 index 40b2dc986..000000000 --- a/tools/make-dict-list/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -apply plugin: "java" -apply plugin: 'kotlin' - -ext { - javaMainClass = "tools.dict.MakeDictList" -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -tasks.register('makeDictList', JavaExec) { - args project.rootProject.project('app').projectDir.path + File.separator + 'src' + - File.separator + 'main' + File.separator + 'assets' - classpath = sourceSets.main.runtimeClasspath - main = javaMainClass -} diff --git a/tools/make-dict-list/src/main/kotlin/tools/dict/MakeDictList.kt b/tools/make-dict-list/src/main/kotlin/tools/dict/MakeDictList.kt deleted file mode 100644 index 7ca864ee6..000000000 --- a/tools/make-dict-list/src/main/kotlin/tools/dict/MakeDictList.kt +++ /dev/null @@ -1,63 +0,0 @@ -package tools.dict - -import java.io.File -import java.net.URL - -class MakeDictList { - - companion object { - - @JvmStatic fun main(args: Array) { - val readmeUrl = "https://codeberg.org/Helium314/aosp-dictionaries/raw/branch/main/README.md" - val readmeText = URL(readmeUrl).readText() - val fileText = doIt(readmeText) - val targetDir = args[0] - - File(targetDir).mkdirs() - File("$targetDir/dictionaries_in_dict_repo.csv").writeText(fileText) - } - - } -} - -/** - * extract dictionary list from README.md - * output format: ,, - * is empty if dictionary is not experimental, no other check done - * requires README.md to have dicts in correct "# Dictionaries" or "# Experimental dictionaries" sections - */ -private fun doIt(readme: String): String { - // output format: ,, - // experimental is empty if dictionary is not experimental, no other check done - var mode = MODE_NOTHING - val outLines = mutableListOf() - readme.split("\n").forEach { line -> - if (line.startsWith("#")) { - mode = if (line.trim() == "# Dictionaries") - MODE_NORMAL - else if (line.trim() == "# Experimental dictionaries") - MODE_EXPERIMENTAL - else - MODE_NOTHING - return@forEach - } - if (mode == MODE_NOTHING || !line.startsWith("*")) return@forEach - val dictName = line.substringAfter("]").substringAfter("(").substringBefore(")") - .substringAfterLast("/").substringBefore(".dict") - val type = dictName.substringBefore("_") - val rawLocale = dictName.substringAfter("_") - val locale = if ("_" !in rawLocale) rawLocale - else { - val split = rawLocale.split("_").toMutableList() - if (!split[1].startsWith("#")) - split[1] = split[1].uppercase() - split.joinToString("_") - } - outLines.add("$type,$locale,${if (mode == MODE_EXPERIMENTAL) "exp" else ""}") - } - return outLines.joinToString("\n") + "\n" -} - -private const val MODE_NOTHING = 0 -private const val MODE_NORMAL = 1 -private const val MODE_EXPERIMENTAL = 2 diff --git a/release.py b/tools/release.py similarity index 52% rename from release.py rename to tools/release.py index 2948fcc9b..3b8c647d7 100755 --- a/release.py +++ b/tools/release.py @@ -2,6 +2,7 @@ import os import subprocess +import sys import zipfile from urllib.request import urlretrieve @@ -10,7 +11,9 @@ def check_git(): result = subprocess.run(["git", "diff", "--name-only"], capture_output=True) if result.returncode != 0 or len(result.stdout) != 0: - raise ValueError("uncommitted changes") + cont = input("uncommitted changes found, continue? [y/N] ") + if cont != "y": + sys.exit() # download and update translations @@ -36,22 +39,65 @@ def check_default_values_diff(): raise ValueError("default strings changed after translation import, something is wrong") -# run that task +def read_dicts_readme() -> list[str]: + dicts_readme_file = "../dictionaries/README.md" + if os.path.isfile(dicts_readme_file): + f = open(dicts_readme_file) + lines = f.readlines() + f.close() + return lines + readme_url = "https://codeberg.org/Helium314/aosp-dictionaries/raw/branch/main/README.md" + tmp_readme = "dicts_readme_tmp.md" + urlretrieve(readme_url, tmp_readme) + f = open(tmp_readme) + lines = f.readlines() + f.close() + os.remove(tmp_readme) + return lines + + +# generate a list of dictionaries available in the dictionaries repository at (https://codeberg.org/Helium314/aosp-dictionaries +# for convenient linking when adding dictionaries in HeliBoard. def update_dict_list(): -# gradle = "gradlew" # Linux -# gradle = "gradlew.bat" # Windows - gradle = "../../builder/realgradle.sh" # weird path for historic reasons - result = subprocess.run([gradle, ":tools:make-dict-list:makeDictList"]) # todo: replace with python code - assert result.returncode == 0 + lines = read_dicts_readme() + mode = 0 + dicts = [] + for line in lines: + line = line.strip() + if line.startswith("#"): + if line == "# Dictionaries": + mode = 1 + elif line == "# Experimental dictionaries": + mode = 2 + else: + mode = 0 + if mode == 0 or not line.startswith("*"): + continue + dict_name = line.split("]")[1].split("(")[1].split(")")[0].split("/")[-1].split(".dict")[0] + (dict_type, locale) = dict_name.split("_", 1) + if "_" in locale: + sp = locale.split("_") + locale = sp[0] + for s in sp[1:]: + locale = locale + "_" + s.upper() + if mode == 2: + dicts.append(f"{dict_type},{locale},exp\n") + else: + dicts.append(f"{dict_type},{locale},\n") + target_file = "app/src/main/assets/dictionaries_in_dict_repo.csv" + with open(target_file, 'w') as f: + f.writelines(dicts) # check whether there is a changelog file for current version and print result and version code def check_changelog(): changelog_dir = "fastlane/metadata/android/en-US/changelogs" assert os.path.isdir(changelog_dir) - filenames = list(os.scandir(changelog_dir)) + filenames = [] + for file in os.scandir(changelog_dir): + filenames.append(file.name) filenames.sort() - changelog_version = filenames[-1].name.replace(".txt", "") + changelog_version = filenames[-1].replace(".txt", "") version = "" with open("app/build.gradle") as f: for line in f: @@ -66,6 +112,8 @@ def check_changelog(): def main(): + if os.getcwd().endswith("tools"): + os.chdir("../") check_git() update_translations() check_default_values_diff() From b9451e4f097538036ad2a5c01ea33e83051b49dd Mon Sep 17 00:00:00 2001 From: codokie <151087174+codokie@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:32:12 +0300 Subject: [PATCH 03/92] auto show / hide toolbar (#674) Co-authored-by: codokie <@> Co-authored-by: Helium314 Co-authored-by: Helium314 --- .../helium314/keyboard/latin/LatinIME.java | 32 ++++++++++++++++--- .../keyboard/latin/settings/Settings.java | 2 ++ .../latin/settings/SettingsValues.java | 4 +++ .../suggestions/SuggestionStripView.java | 8 ++++- app/src/main/res/values/strings.xml | 8 +++++ .../main/res/xml/prefs_screen_correction.xml | 14 ++++++++ 6 files changed, 63 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 82e19e796..bafd0f3f0 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -353,6 +353,10 @@ public boolean hasPendingUpdateSuggestions() { return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); } + public boolean hasPendingResumeSuggestions() { + return hasMessages(MSG_RESUME_SUGGESTIONS); + } + public boolean hasPendingReopenDictionaries() { return hasMessages(MSG_REOPEN_DICTIONARIES); } @@ -989,7 +993,9 @@ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restart // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to // work around this bug. mInputLogic.mConnection.tryFixLyingCursorPosition(); - mHandler.postResumeSuggestions(true /* shouldDelay */); + if (mInputLogic.mConnection.isCursorTouchingWord(currentSettingsValues.mSpacingAndPunctuations, true)) { + mHandler.postResumeSuggestions(true /* shouldDelay */); + } needToCallLoadKeyboardLater = false; } } else { @@ -1020,9 +1026,13 @@ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restart } // This will set the punctuation suggestions if next word suggestion is off; // otherwise it will clear the suggestion strip. - setNeutralSuggestionStrip(); - - mHandler.cancelUpdateSuggestionStrip(); + if (!mHandler.hasPendingResumeSuggestions()) { + mHandler.cancelUpdateSuggestionStrip(); + setNeutralSuggestionStrip(); + if (hasSuggestionStripView() && currentSettingsValues.mAutoShowToolbar) { + mSuggestionStripView.setToolbarVisibility(true); + } + } mainKeyboardView.setMainDictionaryAvailability(mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn); @@ -1593,6 +1603,10 @@ private void setSuggestedWords(final SuggestedWords suggestedWords) { || noSuggestionsFromDictionaries) { mSuggestionStripView.setSuggestions(suggestedWords, mRichImm.getCurrentSubtype().isRtlSubtype()); + // Auto hide the toolbar if dictionary suggestions are available + if (currentSettingsValues.mAutoHideToolbar && !noSuggestionsFromDictionaries) { + mSuggestionStripView.setToolbarVisibility(false); + } } } @@ -1634,6 +1648,8 @@ public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) { // This will show either an empty suggestion strip (if prediction is enabled) or // punctuation suggestions (if it's disabled). + // The toolbar will be shown automatically if the relevant setting is enabled + // and there is a selection of text or it's the start of a line. @Override public void setNeutralSuggestionStrip() { final SettingsValues currentSettings = mSettings.getCurrent(); @@ -1641,6 +1657,14 @@ public void setNeutralSuggestionStrip() { ? SuggestedWords.getEmptyInstance() : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; setSuggestedWords(neutralSuggestions); + if (hasSuggestionStripView() && currentSettings.mAutoShowToolbar) { + final int codePointBeforeCursor = mInputLogic.mConnection.getCodePointBeforeCursor(); + if (mInputLogic.mConnection.hasSelection() + || codePointBeforeCursor == Constants.NOT_A_CODE + || codePointBeforeCursor == Constants.CODE_ENTER) { + mSuggestionStripView.setToolbarVisibility(true); + } + } } @Override diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 9940127d7..59a214e9c 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -149,6 +149,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_QUICK_PIN_TOOLBAR_KEYS = "quick_pin_toolbar_keys"; public static final String PREF_PINNED_TOOLBAR_KEYS = "pinned_toolbar_keys"; public static final String PREF_TOOLBAR_KEYS = "toolbar_keys"; + public static final String PREF_AUTO_SHOW_TOOLBAR = "auto_show_toolbar"; + public static final String PREF_AUTO_HIDE_TOOLBAR = "auto_hide_toolbar"; public static final String PREF_CLIPBOARD_TOOLBAR_KEYS = "clipboard_toolbar_keys"; // Emoji diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index d00c5b601..58dc792a1 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -107,6 +107,8 @@ public class SettingsValues { public final float mKeyboardHeightScale; public final boolean mUrlDetectionEnabled; public final float mBottomPaddingScale; + public final boolean mAutoShowToolbar; + public final boolean mAutoHideToolbar; // From the input box @NonNull @@ -241,6 +243,8 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mSpacingAndPunctuations = new SpacingAndPunctuations(res, mUrlDetectionEnabled); mBottomPaddingScale = prefs.getFloat(Settings.PREF_BOTTOM_PADDING_SCALE, DEFAULT_SIZE_SCALE); mLongPressSymbolsForNumpad = prefs.getBoolean(Settings.PREFS_LONG_PRESS_SYMBOLS_FOR_NUMPAD, false); + mAutoShowToolbar = prefs.getBoolean(Settings.PREF_AUTO_SHOW_TOOLBAR, true); + mAutoHideToolbar = readSuggestionsEnabled(prefs) && prefs.getBoolean(Settings.PREF_AUTO_HIDE_TOOLBAR, true); mHasCustomFunctionalLayout = CustomLayoutUtilsKt.hasCustomFunctionalLayout(selectedSubtype, context); } diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java index c63cd2d6f..311781fef 100644 --- a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java @@ -257,7 +257,7 @@ private void updateKeys() { ? km.isDeviceLocked() : km.isKeyguardLocked(); mToolbarExpandKey.setOnClickListener(hideToolbarKeys ? null : this); - mPinnedKeys.setVisibility(hideToolbarKeys ? GONE : VISIBLE); + mPinnedKeys.setVisibility(hideToolbarKeys ? GONE : mSuggestionsStrip.getVisibility()); isInlineAutofillSuggestionsVisible = false; } @@ -285,6 +285,8 @@ public void setInlineSuggestionsView(final View view) { clear(); isInlineAutofillSuggestionsVisible = true; mSuggestionsStrip.addView(view); + if (Settings.getInstance().getCurrent().mAutoHideToolbar) + setToolbarVisibility(false); } @Override @@ -482,6 +484,10 @@ private void removeSuggestion(TextView wordView) { mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions( getContext(), mSuggestedWords, mSuggestionsStrip, SuggestionStripView.this); mStripVisibilityGroup.showSuggestionsStrip(); + // Show the toolbar if no suggestions are left and the "Auto show toolbar" setting is enabled + if (mSuggestedWords.isEmpty() && Settings.getInstance().getCurrent().mAutoShowToolbar){ + setToolbarVisibility(true); + } } boolean showMoreSuggestions() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21806019e..f0d55bfca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -830,6 +830,14 @@ New dictionary: Variable toolbar direction Reverse direction when a right-to-left keyboard subtype is selected + + Auto show toolbar + + Show the toolbar if input starts or text is selected + + Auto hide toolbar + + Hide the toolbar when suggestions become available Content copied diff --git a/app/src/main/res/xml/prefs_screen_correction.xml b/app/src/main/res/xml/prefs_screen_correction.xml index 0edb993cf..4b0f9aeae 100644 --- a/app/src/main/res/xml/prefs_screen_correction.xml +++ b/app/src/main/res/xml/prefs_screen_correction.xml @@ -108,6 +108,20 @@ android:defaultValue="@bool/config_center_suggestion_text_to_enter" android:persistent="true" /> + + + + Date: Sat, 1 Jun 2024 22:18:19 +0200 Subject: [PATCH 04/92] move new show/hide toolbar settings and do some cleanup --- CONTRIBUTING.md | 4 ---- app/src/main/res/xml/prefs_screen_correction.xml | 14 -------------- app/src/main/res/xml/prefs_screen_toolbar.xml | 14 ++++++++++++++ settings.gradle | 1 - tools/diacritics.py | 15 +++++++++++++-- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71ebd725f..e00203875 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,10 +30,6 @@ See [layouts.md](layouts.md#adding-new-layouts--languages) for how to add new la See make-emoji-keys tool [README](tools/make-emoji-keys/README.md). -# Update List of Existing Dictionaries - -See make-dict-list tool [README](tools/make-dict-list/README.md). - # Translations Translations can be added using [Weblate](https://translate.codeberg.org/projects/heliboard/). You will need an account to update translations and add languages. Add the language you want to translate to in Languages -> Manage translated languages in the top menu bar. Updating translations in a PR will not be accepted, as it may cause conflicts with Weblate translations. diff --git a/app/src/main/res/xml/prefs_screen_correction.xml b/app/src/main/res/xml/prefs_screen_correction.xml index 4b0f9aeae..0edb993cf 100644 --- a/app/src/main/res/xml/prefs_screen_correction.xml +++ b/app/src/main/res/xml/prefs_screen_correction.xml @@ -108,20 +108,6 @@ android:defaultValue="@bool/config_center_suggestion_text_to_enter" android:persistent="true" /> - - - - + + + + list[str]: @@ -25,6 +33,9 @@ def check_diacritics(language: str, diacritics: list[str], all_diacritics: set[s word_lists = find_word_lists(language) if len(word_lists) == 0: return + report_file = f"diacritics_report_{language}.txt" + if os.path.isfile(report_file): + return for dia in diacritics: all_diacritics.remove(dia) foreign_dia = "".join(all_diacritics) @@ -55,7 +66,7 @@ def check_diacritics(language: str, diacritics: list[str], all_diacritics: set[s dia_results = dia_results + f"language diacritics counts: {dia_count}\n" dia_results = dia_results + "foreign diacritics:\n" dia_results = dia_results + "\n".join(foreigns) - with open(f"diacritics_report_{language}.txt", 'w') as f: + with open(report_file, 'w') as f: f.write(dia_results) @@ -70,7 +81,7 @@ def make_all_diacritics(dia_lists: list[list[str]]) -> set[str]: def read_diacritics() -> dict[str, list[str]]: d = dict() language = "" - with open("diacritics.txt") as f: + with open(diacritics_file) as f: for line in f: if language == "": language = line.strip() From a1d32b84fd227c7c2ca58d25c0de57546d313f1c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 1 Jun 2024 22:27:41 +0200 Subject: [PATCH 05/92] separate language switch key behavior from key enablement --- app/src/main/java/helium314/keyboard/latin/App.kt | 4 ++++ .../helium314/keyboard/latin/settings/Settings.java | 1 + .../keyboard/latin/settings/SettingsValues.java | 6 ++++-- app/src/main/res/values/donottranslate.xml | 2 -- app/src/main/res/values/strings.xml | 2 ++ app/src/main/res/xml/prefs_screen_preferences.xml | 10 ++++++++-- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/App.kt b/app/src/main/java/helium314/keyboard/latin/App.kt index cba11f306..a2a83c082 100644 --- a/app/src/main/java/helium314/keyboard/latin/App.kt +++ b/app/src/main/java/helium314/keyboard/latin/App.kt @@ -88,6 +88,10 @@ fun checkVersionUpgrade(context: Context) { .distinctBy { it.split(",").first() } .joinToString(";") prefs.edit { putString(Settings.PREF_PINNED_TOOLBAR_KEYS, newPinnedKeysPref) } + + // enable language switch key if it was enabled previously + if (prefs.contains(Settings.PREF_LANGUAGE_SWITCH_KEY) && prefs.getString(Settings.PREF_LANGUAGE_SWITCH_KEY, "") != "off") + prefs.edit { putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true) } } upgradeToolbarPrefs(prefs) onCustomLayoutFileListChanged() // just to be sure diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 59a214e9c..f352a742a 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -92,6 +92,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "use_personalized_dicts"; public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD = "use_double_space_period"; public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = "block_potentially_offensive"; + public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = "show_language_switch_key"; public static final String PREF_LANGUAGE_SWITCH_KEY = "language_switch_key"; public static final String PREF_SHOW_EMOJI_KEY = "show_emoji_key"; public static final String PREF_VARIABLE_TOOLBAR_DIRECTION = "var_toolbar_direction"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index 58dc792a1..4e59c97c2 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -65,6 +65,7 @@ public class SettingsValues { public final boolean mShowsVoiceInputKey; public final boolean mLanguageSwitchKeyToOtherImes; public final boolean mLanguageSwitchKeyToOtherSubtypes; + private final boolean mShowsLanguageSwitchKey; public final boolean mShowsNumberRow; public final boolean mLocalizedNumberRow; public final boolean mShowsHints; @@ -150,9 +151,10 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mSlidingKeyInputPreviewEnabled = prefs.getBoolean( DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); mShowsVoiceInputKey = mInputAttributes.mShouldShowVoiceInputKey; - final String languagePref = prefs.getString(Settings.PREF_LANGUAGE_SWITCH_KEY, "off"); + final String languagePref = prefs.getString(Settings.PREF_LANGUAGE_SWITCH_KEY, "internal"); mLanguageSwitchKeyToOtherImes = languagePref.equals("input_method") || languagePref.equals("both"); mLanguageSwitchKeyToOtherSubtypes = languagePref.equals("internal") || languagePref.equals("both"); + mShowsLanguageSwitchKey = prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, false); // only relevant for default functional key layout mShowsNumberRow = prefs.getBoolean(Settings.PREF_SHOW_NUMBER_ROW, false); mLocalizedNumberRow = prefs.getBoolean(Settings.PREF_LOCALIZED_NUMBER_ROW, true); mShowsHints = prefs.getBoolean(Settings.PREF_SHOW_HINTS, true); @@ -287,7 +289,7 @@ public boolean shouldInsertSpacesAutomatically() { } public boolean isLanguageSwitchKeyEnabled() { - if (!mLanguageSwitchKeyToOtherImes && !mLanguageSwitchKeyToOtherSubtypes) { + if (!mShowsLanguageSwitchKey) { return false; } final RichInputMethodManager imm = RichInputMethodManager.getInstance(); diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 184afc61c..69d2ab0f2 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -88,13 +88,11 @@ false - off internal input_method both - @string/auto_correction_threshold_mode_off @string/switch_language @string/language_switch_key_switch_input_method @string/language_switch_key_switch_both diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f0d55bfca..fb146b91f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,6 +58,8 @@ Switch both Language switch key + + Language switch key behavior Emoji key diff --git a/app/src/main/res/xml/prefs_screen_preferences.xml b/app/src/main/res/xml/prefs_screen_preferences.xml index e233f9786..c42717ca5 100644 --- a/app/src/main/res/xml/prefs_screen_preferences.xml +++ b/app/src/main/res/xml/prefs_screen_preferences.xml @@ -79,12 +79,18 @@ android:defaultValue="true" android:persistent="true" /> + + From bc1e4d52a826d1f7527ea679d44da25afbc382b8 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 1 Jun 2024 23:15:41 +0200 Subject: [PATCH 06/92] adjust alphabet for AlphabetIndexer, might help with #803 --- .../keyboard/latin/settings/UserDictionarySettings.java | 3 ++- app/src/main/res/values-pl/strings.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java b/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java index f019286e6..53fd5210b 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java @@ -33,6 +33,7 @@ import helium314.keyboard.latin.R; import helium314.keyboard.latin.common.LocaleUtils; +import helium314.keyboard.latin.utils.RunInLocaleKt; import java.util.Locale; @@ -323,7 +324,7 @@ public MyAdapter(final Context context, final int layout, final Cursor c, super(context, layout, c, from, to, 0 /* flags */); if (null != c) { - final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet); + final String alphabet = RunInLocaleKt.runInLocale(context, mLocale, (ctx) -> ctx.getString(R.string.user_dict_fast_scroll_alphabet)); final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3ee18f428..6d1667822 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -117,7 +117,7 @@ "Brak słów w słowniku użytkownika. Aby dodać słowo, kliknij przycisk Dodaj (+)." "Dla wszystkich języków" "Więcej języków…" - " ABCDEFGHIJKLMNOPQRSTUVWXYZ" + " ABCDEĘFGHIJKLŁMNOÓPQRSŚTUVWXYZŻ" Korekty Różne %s min. From 53c66b8b844407b864a42a448f44646bc07694e9 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 2 Jun 2024 14:03:09 +0200 Subject: [PATCH 07/92] fix toolbar being visible when device is locked and auto-show toolbar is on --- .../latin/suggestions/SuggestionStripView.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java index 311781fef..28f93c63e 100644 --- a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java @@ -687,7 +687,15 @@ protected void onSizeChanged(final int w, final int h, final int oldw, final int } public void setToolbarVisibility(final boolean visible) { - if (visible) { + final KeyguardManager km = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); + final boolean locked = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 + ? km.isDeviceLocked() + : km.isKeyguardLocked(); + if (locked) { + mPinnedKeys.setVisibility(GONE); + mSuggestionsStrip.setVisibility(VISIBLE); + mToolbarContainer.setVisibility(GONE); + } else if (visible) { mPinnedKeys.setVisibility(GONE); mSuggestionsStrip.setVisibility(GONE); mToolbarContainer.setVisibility(VISIBLE); @@ -696,7 +704,7 @@ public void setToolbarVisibility(final boolean visible) { mSuggestionsStrip.setVisibility(VISIBLE); mPinnedKeys.setVisibility(VISIBLE); } - mToolbarExpandKey.setScaleX((visible ? -1f : 1f) * mRtl); + mToolbarExpandKey.setScaleX((visible && !locked ? -1f : 1f) * mRtl); } private void addKeyToPinnedKeys(final ToolbarKey pinnedKey) { From 7b7fcb18c4297297f9b4e1e434ef93ba00227111 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 2 Jun 2024 14:04:53 +0200 Subject: [PATCH 08/92] disable auto-show/hide toolbar by default to be consistent with previous behavior --- .../helium314/keyboard/latin/settings/SettingsValues.java | 4 ++-- app/src/main/res/xml/prefs_screen_toolbar.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index 4e59c97c2..bfa8e6c64 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -245,8 +245,8 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mSpacingAndPunctuations = new SpacingAndPunctuations(res, mUrlDetectionEnabled); mBottomPaddingScale = prefs.getFloat(Settings.PREF_BOTTOM_PADDING_SCALE, DEFAULT_SIZE_SCALE); mLongPressSymbolsForNumpad = prefs.getBoolean(Settings.PREFS_LONG_PRESS_SYMBOLS_FOR_NUMPAD, false); - mAutoShowToolbar = prefs.getBoolean(Settings.PREF_AUTO_SHOW_TOOLBAR, true); - mAutoHideToolbar = readSuggestionsEnabled(prefs) && prefs.getBoolean(Settings.PREF_AUTO_HIDE_TOOLBAR, true); + mAutoShowToolbar = prefs.getBoolean(Settings.PREF_AUTO_SHOW_TOOLBAR, false); + mAutoHideToolbar = readSuggestionsEnabled(prefs) && prefs.getBoolean(Settings.PREF_AUTO_HIDE_TOOLBAR, false); mHasCustomFunctionalLayout = CustomLayoutUtilsKt.hasCustomFunctionalLayout(selectedSubtype, context); } diff --git a/app/src/main/res/xml/prefs_screen_toolbar.xml b/app/src/main/res/xml/prefs_screen_toolbar.xml index 7367f31e1..19915ef9e 100644 --- a/app/src/main/res/xml/prefs_screen_toolbar.xml +++ b/app/src/main/res/xml/prefs_screen_toolbar.xml @@ -32,14 +32,14 @@ android:key="auto_show_toolbar" android:title="@string/auto_show_toolbar" android:summary="@string/auto_show_toolbar_summary" - android:defaultValue="true" + android:defaultValue="false" android:persistent="true" /> Date: Sun, 2 Jun 2024 15:07:05 +0200 Subject: [PATCH 09/92] update translations --- CONTRIBUTING.md | 6 +- app/src/main/res/values-ar/strings.xml | 39 +- app/src/main/res/values-da/strings.xml | 15 +- app/src/main/res/values-es-rUS/strings.xml | 13 + app/src/main/res/values-eu/strings.xml | 6 + app/src/main/res/values-fr/strings.xml | 13 + app/src/main/res/values-gl/strings.xml | 9 + app/src/main/res/values-nl/strings.xml | 365 ++++++++++++++---- app/src/main/res/values-pl/strings.xml | 14 + .../metadata/android/ar/changelogs/2000.txt | 9 + .../android/gl-ES/changelogs/2000.txt | 10 + .../android/nl-NL/changelogs/1001.txt | 20 + .../android/nl-NL/changelogs/1003.txt | 9 + .../android/nl-NL/changelogs/1004.txt | 7 + .../android/nl-NL/changelogs/2000.txt | 10 + .../android/nl-NL/full_description.txt | 29 ++ .../android/nl-NL/short_description.txt | 1 + fastlane/metadata/android/nl-NL/title.txt | 1 + .../android/pl-PL/changelogs/2000.txt | 10 + 19 files changed, 514 insertions(+), 72 deletions(-) create mode 100644 fastlane/metadata/android/ar/changelogs/2000.txt create mode 100644 fastlane/metadata/android/gl-ES/changelogs/2000.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/1001.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/1003.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/1004.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/2000.txt create mode 100644 fastlane/metadata/android/nl-NL/full_description.txt create mode 100644 fastlane/metadata/android/nl-NL/short_description.txt create mode 100644 fastlane/metadata/android/nl-NL/title.txt create mode 100644 fastlane/metadata/android/pl-PL/changelogs/2000.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e00203875..675a84177 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,11 +8,11 @@ Once everything is up correctly, you're ready to go! # Guidelines HeliBoard is a complex application, when contributing, you must take a step back and make sure your contribution: -- **Is actually wanted**. Best check related open issues before you start working on a PR. Issues with "PR" and "contributor needed" labels are accepted, but still it would be good if you announced that you are working on it. +- **Is actually wanted**. Best check related open issues before you start working on a PR. Issues with "PR" and "contributor needed" labels are accepted, but still it would be good if you announced that you are working on it, so we can discuss how changes are best implemented. If there is no issue related to your intended contribution, it's a good idea to open a new one to avoid disappointment of the contribution not being accepted. For small changes or fixing obvious bugs this step is not necessary. -- **Is only about a single thing**. Mixing unrelated contributions into a single PR is hard to review and can get messy. +- **Is only about a single thing**. Mixing unrelated or semi-related contributions into a single PR is hard to review and can get messy. - **Is finished or a draft**. When you keep changing the PR without reviewer's feedback, any attempt to review it is doomed and a waste of time. Better mark it as a draft in this case. -- **Has a proper description**. What your contribution does is usually less obvious to reviewers than for yourself. A good description helps _a lot_ for understanding what is going on, and for separating wanted from unintended changes in behavior. +- **Has a proper description**. What your contribution does is usually less obvious to reviewers than for yourself. A good description helps _a lot_ for understanding what is going on, and for separating wanted from unintended changes in behavior. Therefore the changes should be as described, not more and not less. - **Uses already in-place mechanism and take advantage of them**. In other terms, does not reinvent the wheel or uses shortcuts that could alter the consistency of the existing code. The contribution should only add as little complexity as necessary, the code is overly complicated already 😶. - **Has a low footprint**. Some parts of the code are executed very frequently, and the keyboard should stay responsive even on older devices. - **Does not bring any non-free code or proprietary binary blobs**. This also applies to code/binaries with unknown licenses. Make sure you do not introduce any closed-source library from Google. diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index ff6b291d4..0e5aa875a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -330,7 +330,40 @@ خطأ: البرنامج النصي غير متوافق مع لوحة المفاتيح هذه إظهار المتغيرات المحددة بلغات لوحة المفاتيح (افتراضي) مقياس الحشو السفلي - ► يؤدي الضغط لفترة طويلة على مفتاح الحافظة (المفتاح الاختياري الموجود في شريط الاقتراحات) إلى لصق محتويات حافظة النظام. <br> <br> ► يؤدي الضغط لفترة طويلة على المفاتيح في شريط أدوات شريط الاقتراحات إلى تثبيتها في شريط الاقتراحات. <br> <br> ► اضغط لفترة طويلة على مفتاح الفاصلة للوصول إلى عرض الحافظة، أو عرض الرموز التعبيرية، أو الوضع بيد واحدة، أو الإعدادات، أو تبديل اللغة: <br> • سيختفي عرض الرموز التعبيرية وتبديل اللغة إذا كان لديك المفتاح المقابل ممكّن؛ <br> • بالنسبة لبعض التخطيطات، لا يكون مفتاح الفاصلة هو المفتاح، بل المفتاح الموجود في نفس الموضع (على سبيل المثال، يكون \"q\" لتخطيط دفوراك). <br> <br> ► عند تمكين وضع التصفح المتخفي، لن يتم تعلم أي كلمات، ولن تتم إضافة أي رموز تعبيرية إلى الأحدث. <br> <br> ► اضغط على أيقونة التصفح المتخفي للوصول إلى شريط الأدوات. <br> <br> ► إدخال المفتاح المنزلق: اسحب من مفتاح Shift إلى مفتاح آخر لكتابة مفتاح واحد كبير: <br> • يعمل هذا أيضًا مع المفتاح \"?123\" لكتابة رمز واحد من لوحة مفاتيح الرموز، ولـ المفاتيح ذات الصلة. <br> <br> ► اضغط لفترة طويلة على أحد الاقتراحات في شريط الاقتراحات لإظهار المزيد من الاقتراحات، ثم اضغط على زر الحذف لإزالة هذا الاقتراح. <br> <br> ► اسحب لأعلى من أحد الاقتراحات لفتح المزيد من الاقتراحات، ثم حرر الاقتراح لتحديده. <br> <br> ► اضغط لفترة طويلة على أحد الإدخالات في سجل الحافظة لتثبيته (احتفظ به في الحافظة حتى تقوم بإلغاء تثبيته). <br> <br> ► يمكنك إضافة قواميس عن طريق فتحها في مستكشف الملفات: <br> • يعمل هذا فقط مع <i>content-uris</i> وليس مع <i>file-uris</i> مما يعني أنه قد لا يعمل مع بعض مستكشفات الملفات. <br> <br> <i>وضع التصحيح / تصحيح APK</i> <br> <br> • اضغط لفترة طويلة على اقتراح لإظهار القاموس المصدر.<br> <br> • عند استخدام تصحيح APK، يمكنك ابحث عن إعدادات التصحيح ضمن التفضيلات المتقدمة، على الرغم من أن الفائدة محدودة باستثناء تفريغ القواميس في السجل. <br> <br> • في حالة تعطل التطبيق، سيتم سؤالك عما إذا كنت تريد سجلات التعطل عند فتح الإعدادات. <br> <br> • عند استخدام الكتابة متعددة اللغات، سيُظهر شريط المسافة قيمة الثقة المستخدمة لتحديد اللغة المستخدمة حاليًا. <br> <br> • ستحتوي الاقتراحات على بعض الأرقام الصغيرة في الأعلى لتظهر بعض النتائج الداخلية وقاموس المصدر (يمكن تعطيلها). <br> <br> ► بالنسبة للمستخدمين الذين يقومون بالنسخ الاحتياطي اليدوي مع الوصول إلى الجذر: بدءًا من Android 7، لا يكون ملف التفضيلات المشتركة في الموقع الافتراضي، لأن التطبيق يستخدم %s. <br> يعد ذلك ضروريًا حتى يمكن قراءة الإعدادات قبل إلغاء قفل الجهاز، على سبيل المثال. في التمهيد. <br> الملف موجود في /data/user_de/0/package_id/shared_prefs/، على الرغم من أن هذا قد يعتمد على الجهاز وإصدار Android. + ► يؤدي الضغط لفترة طويلة على مفاتيح شريط الأدوات المثبتة إلى وظائف إضافية: <br> +\n\t • الحافظة &#65515; لصق <br> +\n\t • تحرك لليسار/لليمين &#65515; التحرك بالكامل يسارًا/يمينًا <br> +\n\t • التحرك لأعلى/لأسفل &#65515; الصفحة لأعلى/لأسفل <br> +\n\t • نسخة &#65515; نسخ الكل <br> +\n\t • اختر كلمة &#65515; حدد الكل <br> +\n\t • التراجع &#دة <br> <br> +\n► يؤدي الضغط لفترة طويلة على المفاتيح في شريط أدوات شريط الاقتراحات إلى تثبيتها على شريط الاقتراحات. <br><br> +\n► اضغط لفترة طويلة على مفتاح الفاصلة للوصول إلى عرض الحافظة، أو عرض الرموز التعبيرية، أو الوضع بيد واحدة، أو الإعدادات، أو تبديل اللغة: <br> +\n\t • سيختفي عرض الرموز التعبيرية وتبديل اللغة إذا كان لديك المفتاح المقابل ممكّنًا. <ر> +\n\t • بالنسبة لبعض التخطيطات، فهو ليس مفتاح الفاصلة، ولكنه المفتاح الموجود في نفس الموضع (على سبيل المثال، إنه \\\'q\\\' لتخطيط Dvorak). <br><br> +\n► عند تمكين وضع التصفح المتخفي، لن يتم تعلم أي كلمات، ولن تتم إضافة أي رموز تعبيرية إلى الرسائل الأخيرة. <br><br> +\n► اضغط على أيقونة التصفح المتخفي للوصول إلى شريط الأدوات. <br><br> +\n► إدخال مفتاح منزلق: اسحب من مفتاح Shift إلى مفتاح آخر لكتابة مفتاح واحد كبير: <br> +\n\t • يعمل هذا أيضًا مع المفتاح \\\'?123\\\' لكتابة رمز واحد من لوحة مفاتيح الرموز، وللمفاتيح ذات الصلة. <br><br> +\n► اضغط مع الاستمرار على مفتاح Shift أو مفتاح الرمز، واضغط على مفتاح واحد أو أكثر، ثم حرر مفتاح Shift أو مفتاح الرمز للعودة إلى لوحة المفاتيح السابقة. <br><br> +\n► اضغط لفترة طويلة على أحد الاقتراحات في شريط الاقتراحات لإظهار المزيد من الاقتراحات، ثم اضغط على زر الحذف لإزالة هذا الاقتراح. <br><br> +\n► اسحب لأعلى من أحد الاقتراحات لفتح المزيد من الاقتراحات، ثم حرر الاقتراح لتحديده. <br><br> +\n► اضغط لفترة طويلة على أحد الإدخالات في سجل الحافظة لتثبيته (احتفظ به في الحافظة حتى تقوم بإلغاء تثبيته). <br><br> +\n► اسحب لليسار في عرض الحافظة لإزالة إدخال (باستثناء عندما يكون مثبتًا) <br> <br> +\n► حدد النص واضغط على مفتاح Shift للتبديل بين الأحرف الكبيرة والصغيرة والكلمات الكبيرة. <br><br> +\n► يمكنك إضافة قواميس عن طريق فتحها في مستكشف الملفات: <br> +\n\t • يعمل هذا فقط مع <i>content-uris</i> وليس مع <i>file-uris</i>، مما يعني أنه قد لا يعمل مع بعض مستكشفات الملفات. <br><br> +\n► بالنسبة للمستخدمين الذين يقومون بالنسخ الاحتياطي اليدوي مع الوصول إلى الجذر: <br> +\n\t • بدءًا من Android 7، لا يكون ملف التفضيلات المشتركة في الموقع الافتراضي، لأن التطبيق يستخدم %s. يعد ذلك ضروريًا حتى يمكن قراءة الإعدادات قبل إلغاء قفل الجهاز، على سبيل المثال. في التمهيد؛ <br> +\n\t • الملف موجود في /data/user_de/0/package_id/shared_prefs/ على الرغم من أن هذا قد يعتمد على الجهاز وإصدار Android. <br><br> +\n<i><b>وضع التصحيح / تصحيح APK</b></i> <br> <br> +\n► اضغط لفترة طويلة على اقتراح لإظهار القاموس المصدر. <br><br> +\n► عند استخدام debug APK، يمكنك العثور على إعدادات Debug ضمن التفضيلات المتقدمة، على الرغم من أن الفائدة محدودة باستثناء تفريغ القواميس في السجل. <ر> +\n\t • بالنسبة لإصدار APK، يتعين عليك النقر على الإصدار في <i>حول</i> عدة مرات، ثم يمكنك العثور على إعدادات تصحيح الأخطاء في <i>التفضيلات المتقدمة</i>. <ر> +\n\t • عند تمكين <i>إظهار معلومات الاقتراح</i>، ستحتوي الاقتراحات على بعض الأرقام الصغيرة في الأعلى لإظهار بعض النتائج الداخلية وقاموس المصدر. <br><br> +\n► في حالة تعطل التطبيق، سيُطلب منك ما إذا كنت تريد سجلات التعطل عند فتح الإعدادات. <br><br> +\n► عند استخدام الكتابة متعددة اللغات، سيُظهر شريط المسافة قيمة الثقة المستخدمة لتحديد اللغة المستخدمة حاليًا. <br><br> +\n► ستحتوي الاقتراحات على بعض الأرقام الصغيرة في الأعلى لإظهار بعض النتائج الداخلية وقاموس المصدر (يمكن تعطيله). رخصة جنو العامة v3.0 هذه الكلمة موجودة بالفعل في قاموس المستخدم %s. الرجاء كتابة واحدة أخرى. تفضيل المحلية على الأرقام اللاتينية @@ -354,4 +387,8 @@ مانسي (%s) عرض المزيد من الالوان يعرض هذا الإعداد كافة الألوان المستخدمة داخليًا. قد تتغير قائمة الألوان في أي وقت. لا يوجد لون افتراضي، ولن تتم ترجمة الأسماء. + تخصيص تخطيطات المفاتيح الوظيفية + مفاتيح وظيفية + المفاتيح الوظيفية (المزيد من الرموز) + المفاتيح الوظيفية (الرموز) \ No newline at end of file diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index aad7c5459..27a28b7d9 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -3,8 +3,7 @@ Copyright (C) 2008 The Android Open Source Project modified SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only ---> - +--> "Anvend kontaktnavne" "Stavekontrollen bruger ord fra dine kontaktpersondata" "Vibration ved tastetryk" @@ -168,4 +167,16 @@ "Søg" "Pause" "Vent" + Ignorer andre apps\' forespørgsel om at slå anbefalinger fra (kan skabe problemer) + Backup + Skift begge + Tilføj ord til din personlige ordbog + Brug enhedens personlige ordbog til at gemme de lærte ord + Altid vis anbefalinger + Mere autokorrekt + Autokorrekt selv om der ikke eksplicit er anmodet om det af inputfeltet + Altid brug den midterste anbefaling + Skift sprog + Gendan + Flersproget skrivning \ No newline at end of file diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index af07b3cbc..a050b75e6 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -344,4 +344,17 @@ "Sin diccionario, sólo obtendrá sugerencias para el texto que haya introducido anteriormente.<br> \n Puede descargar los diccionarios %1$s, o comprobar si se puede descargar directamente un diccionario para \"%2$s\" %3$s." ► Pulsando prolongadamente la tecla del portapapeles (la opcional en la tira de sugerencias) se pega el contenido del portapapeles del sistema. <br> <br> ► Al pulsar prolongadamente las teclas de la barra de sugerencias, éstas se fijan a la barra de sugerencias. <br> <br> ► Pulse prolongadamente la tecla Coma para acceder a la vista del portapapeles, la vista de emoji, el modo de una mano, Ajustes o Cambiar idioma: <br> \t• La vista Emoji y el cambio de idioma desaparecerán si tienes activada la tecla correspondiente; <br> \t• Para algunas distribuciones no es la tecla Coma, sino la tecla en la misma posición (por ejemplo, es \'q\' para la distribución Dvorak). <br> <br> ► Cuando el modo incógnito está activado, no se aprende ninguna palabra ni se añaden emojis a los favoritos. <br> <br> ► Pulse el icono Incógnito para acceder a la barra de herramientas. <br> <br> ► Tecla deslizante: Desliza desde Mayúsculas a otra tecla para escribir una sola mayúscula: <br> \t• Esto también funciona con la tecla \'?123\' para escribir un solo símbolo del teclado de símbolos, y con las teclas relacionadas. <br> <br> ► Mantenga pulsada una sugerencia en la tira de sugerencias para mostrar más sugerencias, y el botón suprimir para eliminar esta sugerencia. <br> <br> ► Desliza el dedo hacia arriba desde una sugerencia para abrir más sugerencias y suéltalo para seleccionarla. <br> <br> ► Mantén pulsada una entrada del historial del portapapeles para anclarla (se mantendrá en el portapapeles hasta que la desancles). <br> <br> ► Puedes añadir diccionarios abriéndolos en un explorador de archivos: <br> \t• Esto sólo funciona con <i>content-uris</i> y no con <i>file-uris</i>, lo que significa que puede no funcionar con algunos exploradores de archivos. <br> <br> <i>Modo depuración / depurar APK</i> <br> <br> \t• Pulsa prolongadamente una sugerencia para mostrar el diccionario de origen.<br> <br> \t• Cuando se utiliza el APK de depuración, se puede encontrar la Configuración de Depuración dentro de las Preferencias Avanzadas, aunque la utilidad es limitada excepto para volcar diccionarios en el registro. <br> <br> \t• En caso de que se bloquee una aplicación, se te preguntará si quieres los registros de bloqueo cuando abras la Configuración. <br> <br> \t• Al utilizar la escritura multilingüe, la barra espaciadora mostrará un indicador de confianza utilizado para determinar el idioma utilizado en ese momento. <br> <br> \t• Las sugerencias tendrán unos pequeños números en la parte superior mostrando alguna puntuación interna y el diccionario de fuentes (se puede desactivar). <br> <br> ► Para usuarios que realizan copias de seguridad manuales con acceso root: A partir de Android 7, el archivo de preferencias compartidas no está en la ubicación predeterminada, porque la aplicación está utilizando %s. <br> Esto es necesario para poder leer los ajustes antes de desbloquear el dispositivo, por ejemplo, en el arranque. <br> El archivo se encuentra en /data/user_de/0/package_id/shared_prefs/, aunque esto puede depender del dispositivo y de la versión de Android. + Utilizar siempre la sugerencia del centro + Al presionar espacio o puntuación, se ingresará la sugerencia central + Cerrar el historial del portapapeles + Seleccionar teclas de la barra de herramientas del portapapeles + %s (Extendido) + Mansi + Mansi (%s) + Mostrar más colores + Esta configuración muestra todos los colores que se utilizan internamente. La lista de colores puede cambiar en cualquier momento. No hay un color predeterminado y los nombres no se traducirán. + Teclas funcionales + Teclas funcionales (Más símbolos) + Personalizar diseño de teclas funcionales + Teclas funcionales (Símbolos) \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index ba463fa84..7fb135ad8 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -348,4 +348,10 @@ Zuriunea edo puntuazioa sakatzean, erdiko iradokizuna sartuko da Itxi arbelaren historia Hautatu arbeleko tresna-barrako teklak + Erakutsi kolore gehiago + Ezarpen honek barnean erabiltzen diren kolore guztiak agerian uzten ditu. Koloreen zerrenda edozein unetan alda daiteke. Ez dago kolore lehenetsirik, eta izenak ez dira itzuliko. + Funtzio-teklak + Funtzio-teklak (ikur gehiago) + Funtzio-teklak (ikurrak) + Pertsonalizatu funtzio-teklen diseinua \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 160d44841..a96a98600 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -383,4 +383,17 @@ Nouveau dictionnaire: Mansi (%s) Afficher plus de couleurs Ce paramètre expose toutes les couleurs utilisées en interne. La liste des couleurs peut être modifiée à tout moment. Il n\'y a pas de couleur par défaut et les noms ne seront pas traduits. + Touches fonctionnelles + Touches fonctionnelles (Plus de symboles) + Personnaliser la disposition des touches fonctionnelles + Touches fonctionnelles (Symboles) + Épingler les touches de la barre d\'outils lors d\'un appui-long + Barre d\'outils + Sélectionner les touches épinglées de la barre d\'outils + Cela désactivera les autres actions lors d\'un appui-long sur les touches de la barre d\'outils qui ne sont pas épinglées + Contenu copié + Masquer automatiquement la barre d\'outils + La barre d\'outils est masquée lorsque les suggestions sont disponibles + La barre d\'outils s\'affiche si la saisie débute ou si le texte est sélectionné + Afficher automatiquement la barre d\'outils \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 49a8543d2..5bb7addc6 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -353,4 +353,13 @@ Mansi (%s) Mostrar máis cores Este axuste expón todas as cores que se usan internamente. A lista de cores pode cambiar en calquera momento. Non hai cor por defecto, e os nomes non serán traduciddos. + Teclas función + Teclas función (Máis símbolos) + Personalizar a disposición de teclas función + Teclas función (Símbolos) + Fixa unha tecla na barra mantendo premida + Escolle as teclas da barra fixada + Esto desactivará outras accións de pulsación longa para teclas da barra que non están fixadas + Barra de ferramentas + Copiouse o contido \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f3cbcb30f..d19c1f323 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -3,33 +3,32 @@ Copyright (C) 2008 The Android Open Source Project modified SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only ---> - +--> "Contactnamen opzoeken" "De spellingcontrole gebruikt items uit je contactenlijst" "Trillen bij toetsaanslag" "Geluid bij toetsaanslag" "Pop-up bij toetsaanslag" "Voorkeuren" - "Invoer met bewegingen" + Typen met gebaren "Tekstcorrectie" "Geavanceerd" "Thema" - "Gesplitst toetsenbord inschakelen" - "Invoermeth. overschakelen" - "Schakelknop voor taal" + Gesplitst toetsenbord + Invoermethode schakelen + Taalkeuze-toets "%s ms" "Systeemstandaard" "Contactnamen suggereren" "Namen uit Contacten gebruiken voor suggesties en correcties" "Gepersonaliseerde suggesties" - "Dubbeltik is punt, spatie" - "Dubbeltik op spatiebalk voor een punt gevolgd door een spatie" - "Auto-hoofdlettergebruik" - "Het eerste woord van elke zin met een hoofdletter schrijven" + Dubbel-spatie punt + Dubbele tik op spatiebalk voor een punt gevolgd door een spatie + Automatische hoofdletters + Het eerste woord van elke zin met een hoofdletter "Persoonlijk woordenboek" - "Woordenboeken toevoegen" - "Algemeen woordenboek" + Aanvullende woordenboeken + Hoofdwoordenboek "Correctievoorstellen weergeven" "Voorgestelde woorden weergeven tijdens typen" "Grof taalgebruik blokkeren" @@ -37,37 +36,37 @@ "Autocorrectie" "Met spatiebalk en interpunctie worden verkeerd gespelde woorden automatisch gecorrigeerd" "Uitgeschakeld" - "Normaal" + Bescheiden "Agressief" "Zeer agressief" "Suggesties voor volgend woord" "Het vorige woord gebruiken bij het doen van suggesties" - "Bewegingsinvoer inschakelen" - "Voer een woord in door van letter naar letter te bewegen" - "Gebarenspoor weergeven" + Typen met gebaren inschakelen + Voer een woord in door van letter naar letter te vegen + Veegspoor weergeven "Dynamisch zwevend voorbeeld" - "Het voorgestelde woord weergeven tijdens het tekenen" - "Gebaar voor woordgroep" - "Spaties invoeren door naar de spatietoets te bewegen" + Het voorgestelde woord tonen tijdens het vegen + Woordgroepen gebaren + Spaties invoeren door te vegen over de spatiebalk "Engels (GB)" "Engels (VS)" "Spaans (VS)" "Hindi-Engels" "Servisch (Latijns)" - "Engels (VK) (%s)" - "Engels (VS) (%s)" - "Spaans (VS) (%s)" - "Hindi-Engels (%s)" - "Servisch (%s)" - "%s (traditioneel)" - "%s (compact)" + Engels (VK) (%s) + Engels (VS) (%s) + Spaans (VS) (%s) + Hindi-Engels (%s) + Servisch (%s) + %s (Traditional) + %s (Compact) "Geen taal (alfabet)" "Alfabet (QWERTY)" "Alfabet (QWERTZ)" "Alfabet (AZERTY)" "Alfabet (Dvorak)" "Alfabet (Colemak)" - "Alfabet (pc)" + Alfabet (PC) "Emoji" "Toevoegen" "Verwijderen" @@ -76,37 +75,37 @@ "Lay-out" "Trilingsduur bij toetsgebruik" "Geluidsvolume bij toetsgebruik" - "Lengte toetsinvoer" + Vertraging bij lang drukken "Emoji voor fysiek toetsenbord" - "Emoji-palet weergeven met fysieke Alt-toets" + Emoji-palet weergeven bij fysieke Alt-toets "Standaard" - "Welkom bij %s" - "met Invoer met bewegingen" + Welkom bij %s + met Typen met gebaren "Aan de slag" "Volgende stap" - "%s instellen" - "%s inschakelen" - "Vink \'%s\' aan in de instellingen bij \'Talen en invoer\'. De app kan dan worden uitgevoerd op je apparaat." - "%s is al ingeschakeld in de instellingen bij \'Talen en invoer\', dus deze stap is voltooid. Op naar de volgende!" + %s instellen + %s inschakelen + Vink \'%s\' aan in de instellingen bij \'Talen & invoer\'. De app kan dan worden uitgevoerd op dit apparaat. + %s is al ingeschakeld in de instellingen bij \'Talen & invoer\', dus deze stap is voltooid. Op naar de volgende! "Inschakelen in \'Instellingen\'" - "Overschakelen naar %s" - "Selecteer vervolgens \'%s\' als actieve tekstinvoermethode." + Overschakelen naar %s + Selecteer vervolgens \'%s\' als actieve tekstinvoermethode. "Schakelen tussen invoermethoden" - "Gefeliciteerd, je kunt nu aan de slag." - "Je kunt nu in al je favoriete apps typen met %s." + Gefeliciteerd, je kunt nu aan de slag! + Je kunt nu in al je favoriete apps typen met %s. "Voltooid" "App-pictogram weergeven" "App-pictogram weergeven in het opstartprogramma" - "Woordenboeken toevoegen" + Aanvullende woordenboeken "Instellingen voor woordenboeken" "Woordenboek beschikbaar" "Verbindingsprobleem woordenboekservice" "Geen woordenboeken" "Laatst bijgewerkt" - "Algemeen woordenboek" + Hoofdwoordenboek "Instellingen" "Verwijderen" - "Versie %1$s" + Versie %1$s "Toevoegen" "Toevoegen aan woordenboek" "Sneltoets:" @@ -118,54 +117,288 @@ "Voor alle talen" "Meer talen…" " ABCDEFGHIJKLMNOPQRSTUVWXYZ" - Automatische spatie na punt - "Gebruik systeemtalen" + Automatische spatie na interpunctie + Systeemtalen gebruiken "Invoermethode selecteren" Automatische dag/nachtmodus Het uiterlijk volgt de systeeminstellingen - Klembord geschiedenis + Klembordgeschiedenis Experimenteel - Allerlei + Diversen Geen limiet Bewaartijd van de geschiedenis - Veeg verwijderen + Verwijderen met veegbeweging Klembordgeschiedenis inschakelen Meer toetsen - Toon altijd nummerrij + Nummerrij altijd weergeven Voer een veegbeweging uit vanaf de delete-toets om grotere delen van tekst tegelijk te selecteren en te verwijderen HeliBoard spellingcontrole HeliBoard spellingscontrole instellingen - Nummer rij + Nummerrij Incognitomodus forceren Emoji-toets Correcties Suggesties - %smin. - Als u lang op de spatietoets drukt, wordt het selectiemenu van de invoermethode gevraagd - Vertrouwen in automatische correctie - Indien uitgeschakeld, plakt de klembordtoets de inhoud van het klembord, indien aanwezig - Toon belangrijke hints - Schakel het leren van nieuwe woorden uit + %smin + Druk lang op de spatietoets voor het selectiemenu van de invoermethode + Autocorrectie vertrouwen + Indien uitgeschakeld, plakt de klembord-toets de inhoud van het klembord + Hints bij toetsen weergeven + Het leren van nieuwe woorden uitschakelen Hints voor lang indrukken weergeven - Toetsenbord hoogte schaal - %s (Akhor) - Wijzig de invoermethode met de spatietoets + Toetsenbordhoogte schaal + %s (Akkhor) + Invoermethode schakelen met spatietoets Alfabet (Colemak Mod-DH) Alfabet (Workman) - Toetsen randen - Automatisch spatie invoegen na punt bij het typen van een nieuw woord - HeliBoard Instellingen + Toetsen met randen + Automatisch spatie invoegen na interpunctie en het typen van een nieuw woord + HeliBoard instellingen Invoer Extra toetsen "Ongedaan maken" "Opnieuw" - "Suggesties verbeteren met je communicatie en getypte gegevens" - "Ga" - "Volg." - "Vorig" + Suggesties verbeteren op basis van je communicatie en getypte gegevens + Gaan + Volgende + Vorige "Gereed" - "Verz." + Verzenden "Zoeken" "Pauze" "Wacht" + Enkele handmodus + Roze + Zand + Violet + Aangepast (nacht) + Kleuren aanpassen + Smalle toetsruimte + Het toetsenbord configureren + Scheidingsruimte + Beide wisselen + Woorden toevoegen aan persoonlijk woordenboek + Gebruik het persoonlijke woordenboek van het apparaat om geleerde woorden op te slaan + Waarschuwing: met het uitschakelen van deze instelling worden geleerde gegevens gewist + Altijd suggesties tonen + Automatisch corrigeren, zelfs als dit niet expliciet wordt gevraagd door het invoerveld + Meer autocorrectie + Fout bij back-up: %s + Woordenboek vervangen + Gebruikerswoordenboek \"%1$s\" vervangen\"? +\n +\nHuidig woordenboek: +\n%2$s +\n +\nNieuw woordenboek: +\n%3$s + Kaitag + Altijd middelste suggestie gebruiken + Bij spatie of interpunctie wordt de middelste suggestie ingevoerd + Bibliotheek laden voor typen met gebaren + Zorg voor een passende bibliotheek om typen met gebaren in te schakelen + Back-up + Terugzetten + Opslaan of laden vanuit bestand. Waarschuwing: terugzetten overschrijft bestaande gegevens + Back-up en terugzetten + Meertalig typen + Onbekend bibliotheekbestand. Komt het van een vertrouwde bron en is het geschikt voor \'%s\'? + Bibliotheek laden + Bibliotheek verwijderen + Meer letters met diakritische tekens weergeven in pop-up + Algemene varianten toevoegen + Alle beschikbare varianten toevoegen + URL-detectie + Probeer URL\'s en dergelijke als één woord te detecteren + Talen & lay-outs + Aangepaste nummerrij + Voorkeur voor regionale cijfers i.p.v. latijns + Bron voor hints kiezen + Hints tonen als lang drukken op een toets extra functionaliteit activeert + Volgorde pop-up toets kiezen + Nummerrij + Bodemafstand schaal + Naam van lay-out instellen + Symbolen en nummer-lay-outs aanpassen + Aangepaste lay-out %s verwijderen? + Symbolen (Arabisch) + Aangepast + Horizontaal veeggebaar over spatiebalk + Klembordgeschiedenis sluiten + Klembord-werkbalktoetsen kiezen + Druk lang op de toets symbolen voor het numpad + Bestand laden + Kan bestand niet lezen + Gebruikerswoordenboek \"%s\" verwijderen? + Zwart + Symbolen + %s (experimenteel) + Fout: script niet compatibel met dit toetsenbord + Gewicht: + Bewolkt + %s (Extended) + Taal kiezen + Dit woord is al aanwezig in het gebruikerswoordenboek van %s. Typ er nog een. + Woord toevoegen + Selecteer kleuren voor tekst en achtergronden + Taal wisselen + Taal (prioriteit) + Lay-out + Symbolen + Klembord + Klembord wissen + Woord selecteren + Werkbalktoetsen kiezen + Taal + Geheel links + Geheel rechts + Links + Rechts + Omhoog + Omlaag + Spraakinvoer + Achtergrondafbeelding instellen + Afbeelding voor dag/nachtmodus instellen? + Dag + Nacht + Cijfers + Numpad + Numpad (liggend) + Knippen + %s (Probhat) + Intern hoofdwoordenboek + Over + Versie + Bekijk op GitHub + Logbestand opslaan + Open-source licentie + Tik op de taal om instellingen te openen + Fout: Geselecteerd bestand is geen geldig woordenboekbestand + Telefoonsymbolen + Mansi + Mansi (%s) + Kaitag (%s) + %s (Sebeolsik 390) + %s (Sebeolsik Final) + %s (Student) + Aangepaste lay-out toevoegen + Selecteer een bestand in een compatibel formaat. Informatie over de formaten is beschikbaar %s. + Bestaande lay-out kopiëren + Fout bij lay-out: %s + Tik om lay-out te bewerken + Lay-outs (behalve symbolen) geven interne instellingen weer die mogelijk nog veranderen. Als dat gebeurt, werkt de aangepaste lay-out mogelijk niet meer goed. + Meer symbolen + Telefoon + Woordenboeken + Woordenboek toevoegen vanuit bestand + Aan welke taal moet het woordenboek \"%1$s\" voor %2$s worden toegevoegd? + Toevoegen aan %s + Niet meer laten zien + hier + Toch gebruiken + Fout bij laden van woordenboekbestand + Stijl + Afgerond + Kleuren + Licht + Holo Wit + Donker + Donkerder + Dynamische kleuren + Blauwgrijs + Bruin + Chocolade + Bos + Navigatiebalk kleuren + Kleuren aanpassen (nacht) + Kleuren automatisch kiezen + Alleen hoofdkleuren weergeven + Meer kleuren tonen + Alle kleuren tonen + Deze instelling toont alle kleuren bloot die intern worden gebruikt. De lijst met kleuren kan op elk moment veranderen. Er is geen standaardkleur en de namen worden niet vertaald. + Klik voor een voorbeeld + Toetsenbord-achtergrond + Toetstekst + Toets-achtergrond + Spatiebalk-achtergrond + Spatiebalk-tekst + Accent + GNU General Public License v3.0 + Sluiten + Uiterlijk + Beschrijving van verborgen functies + Toon functies die mogelijk onopgemerkt blijven + apparaatbeveiligde opslag + Toetshint-tekst + Suggestieregel-tekst + Functietoets achtergrond + Gebaren-invoer + Geen + Cursor verplaatsen + Variabele werkbalkrichting + Omgekeerde richting wanneer een rechts-naar-links toetsenbord-subtype is geselecteerd + Verticaal veeggebaar over spatiebalk + Een bibliotheek is nodig voor \'%s\'. Incompatibele bibliotheken kunnen crashen bij het typen van gebaren. +\n +\nWaarschuwing: het laden van externe code kan een veiligheidsrisico vormen. Gebruik alleen een bibliotheek van een vertrouwde bron. + "Zonder woordenboek krijg je alleen suggesties voor tekst die eerder is ingevoerd.<br> +\n Je kunt woordenboeken %1$s downloaden, of een woordenboek voor \"%2$s\" %3$s direct downloaden." + Varianten gedefinieerd in toetsenbordtalen weergeven (standaard) + Functionele hints weergeven + Alfabet (Bépo) + Het geselecteerde bestand is voor %1$s, maar %2$s werd verwacht. Toch gebruiken voor %2$s? + Fout bij terugzetten van de back-up: %s + Selecteer een woordenboek om toe te voegen. Woordenboeken in .dict-indeling kunnen %s worden gedownload. + Kleuren (nacht) + Indigo + Oceaan + ► Lang drukken van vastgezette werkbalktoetsen resulteert in extra functionaliteit: <br> +\n\t• klembord &#65515; plakken<br> +\n\t• links/rechts verplaatsen &#65515; volledig links/rechts verplaatsen <br> +\n\t• omhoog/omlaag verplaatsen &#65515; pagina omhoog/omlaag <br> +\n\t• kopiëren &#65515; alles kopiëren <br> +\n\t• woord selecteren &#65515; alles selecteren<br> +\n\t• ongedaan maken &#8596; opnieuw <br> <br> +\n► Lang drukken van toetsen in de suggestieregel zet deze daarin vast. <br> <br> +\n► Lang drukken van de komma-toets om de klembordweergave, Emoji-weergave, Enkele handmodus, Instellingen, of de taal te wisselen: <br> +\n\t• Emoji-weergave en taal-schakelaar verdwijnen wanneer de corresponderende toets is geactiveerd; <br> +\n\t• Voor sommige lay-outs is het niet de komma-toets, maar de toets op dezelfde positie (bijvoorbeeld \\\'q\\\' bij een Dvorak-lay-out). <br> <br> +\n► Wanneer incognitomodus is ingeschakeld, worden er geen woorden geleerd en worden er geen emoji\'s aan de lijst recent toegevoegd.<br> <br> +\n► Druk op het Incognito-pictogram om toegang te krijgen tot de werkbalk. <br> <br> +\n► Vegende toetsinvoer: Veeg van shift naar een andere toets om een enkele hoofdletter te typen: <br> +\n\t• Dit werkt ook voor de \\\'?123\\\'-toets om een enkel symbool te typen vanaf het toetsenbord van de symbolen en voor gerelateerde toetsen. <br> <br> +\n► Houd de shift- of symbolentoets ingedrukt, druk op een of meer toetsen en laat vervolgens de shift- of symbolentoets los om terug te keren naar het vorige toetsenbord. <br> <br> +\n► Druk lang op een suggestie in de suggestiestrip om meer suggesties te tonen en een verwijderknop om deze suggestie te verwijderen. <br> <br> +\n► Veeg omhoog van een suggestie om meer suggesties te openen en geef de suggestie om het te selecteren vrij. <br> <br> +\n► Druk lang op een item in de klembordgeschiedenis om het vast te pinnen (in het klembord bewaren totdat je het losmaakt). <br> <br> +\n► Veeg links in de klembordweergave om een item te verwijderen (behalve wanneer het vastgemaakt is) <br> <br> +\n► Selecteer tekst en druk op shift om te schakelen tussen hoofdletters, kleine letters en hoofdletters. <br> <br> +\n► Je kunt woordenboeken toevoegen door ze te openen in een bestandsverkenner: <br> +\n\t• Dit werkt alleen met <i>inhoud-uris</i> en niet met <i>bestand-uris</i>, wat betekent dat het mogelijk niet werkt met sommige bestandsverkenners. <br> <br> +\n► Voor gebruikers die handmatige back-ups maken met roottoegang: <br> +\n\t• Vanaf Android 7 bevindt het bestand met gedeelde voorkeuren zich niet op de standaardlocatie, omdat de app %s gebruikt. Dit is nodig zodat de instellingen kunnen worden gelezen voordat het apparaat wordt ontgrendeld, b.v. bij het opstarten; <br> +\n\t• Het bestand bevindt zich in /data/user_de/0/package_id/shared_prefs/ hoewel dit kan afhangen van het apparaat en de Android-versie. <br> <br> +\n<i><b>Debug mode / debug APK</b></i> <br> <br> +\n► Druk lang op een suggestie om het bronwoordenboek te tonen. <br> <br> +\n► Bij gebruik van een Debug APK, kunt u foutopsporingsinstellingen vinden in de geavanceerde voorkeuren, hoewel het nut beperkt is, behalve voor het dumpen van woordenboeken in het logboek. <br> +\n\t• Voor een release-APK moet je meermaals op de versie tikken in <i>Over</i>, dan kunt u debug-instellingen vinden in<i>Geavanceerde voorkeuren</i>. <br> +\n\t• Bij het inschakelen van <i>Suggestie-informatie tonen</i>, zullen suggesties enkele kleine cijfers bevatten met een interne score en een bronwoordenboek. <br> <br> +\n► In het geval van een applicatiecrash, wordt u gevraagd of u de crashlogboeken wilt wanneer u de instellingen opent. <br> <br> +\n► Bij het gebruik van meertalig typen zal de spatiebalk een betrouwbaarheidswaarde tonen die wordt gebruikt voor het bepalen van de momenteel gebruikte taal. <br> <br> +\n► Suggesties hebben een aantal kleine cijfers bovenaan met een interne score en een bronwoordenboek (kan worden uitgeschakeld). + Verzoeken van andere apps om suggesties uit te schakelen negeren (kan problemen veroorzaken) + Functietoetsen + Functietoetsen (symbolen) + Functietoetsen (meer symbolen) + Functietoets-layouts aanpassen + Vastgezette werkbalktoetsen selecteren + Werkbalktoets vastzetten bij lang indrukken + Hiermee worden andere lang indrukkenacties uitgeschakeld voor werkbalktoetsen die niet zijn vastgezet + Werkbalk + Inhoud gekopieerd + Werkbalk automatisch verbergen + Werkbalk automatisch weergeven + Verberg de werkbalk wanneer er suggesties beschikbaar komen + Toon de werkbalk bij invoer of selectie van tekst + Gedrag taalkeuze-toets \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6d1667822..293f527b7 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -386,4 +386,18 @@ mansyjski Pokaż więcej kolorów To ustawienie wyświetla wszystkie kolory używane wewnętrznie. Lista kolorów może w każdej chwili ulec zmianie. Nie ma domyślnego koloru, a nazwy nie zostaną przetłumaczone. + Klawisze funkcyjne + Klawisze funkcyjne (dodatkowe symbole) + Dostosuj układy klawiszy funkcyjnych + Klawisze funkcyjne (symbole) + Przypnij klawisz paska narzędzi po długim naciśnięciu + Pasek narzędzi + Spowoduje to wyłączenie funkcji związanych z długim naciśnięciem klawiszy, które nie zostały przypięte + Wybierz przypięte klawisze paska narzędzi + Skopiowano + Ukryj pasek narzędzi, jeśli są dostępne sugestie + Pokaż pasek narzędzi, jeśli rozpocznie się wprowadzanie lub tekst zostanie zaznaczony + Automatyczne ukrywanie paska narzędzi + Automatyczne wyświetlanie paska narzędzi + Zachowanie klawisza zmiany języka \ No newline at end of file diff --git a/fastlane/metadata/android/ar/changelogs/2000.txt b/fastlane/metadata/android/ar/changelogs/2000.txt new file mode 100644 index 000000000..02ca6ed59 --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/2000.txt @@ -0,0 +1,9 @@ +* إضافة الدعم الأساسي لمفاتيح التعديل +* إضافة وظائف الضغط لفترة طويلة إلى المزيد من مفاتيح شريط الأدوات +* والمزيد من مفاتيح شريط أدوات سجل الحافظة +* جعل شريط أدوات محفوظات الحافظة قابلاً للتخصيص +* السماح بتخصيص كافة الألوان +* إضافة إعداد لإظهار الكلمة التي سيتم إدخالها دائمًا كاقتراح وسط +* إضافة مؤشر قفل الحروف الكبيرة +* إصلاح النص المقطوع في نافذة المعاينة الرئيسية المنبثقة على بعض الأجهزة +* مزيد من الإصلاحات والتحسينات، راجع ملاحظات الإصدار diff --git a/fastlane/metadata/android/gl-ES/changelogs/2000.txt b/fastlane/metadata/android/gl-ES/changelogs/2000.txt new file mode 100644 index 000000000..f946152b0 --- /dev/null +++ b/fastlane/metadata/android/gl-ES/changelogs/2000.txt @@ -0,0 +1,10 @@ +* engadir soporte básico para teclas modificadas +* engadir funcións de pulsación longa a máis teclas da barra +* e máis teclas da barra para historial do portapapeis +* facer personalizable o historial do portapapeis +* permitir personalizar todas as cores +* engadir axuste para mostrar a palabra para ser engadida como suxestión do medio +* engadir indicador de maiúsculas +* engadir disposicións Piedmontese, Eastern Mari, Mansi e disposicións extendidas para Kannada e Húngaro +* arranxo do texto recortado no emerxente dalgunhas teclas en algúns dispositivos +* máis melloras e arranxos, ver notas da versión diff --git a/fastlane/metadata/android/nl-NL/changelogs/1001.txt b/fastlane/metadata/android/nl-NL/changelogs/1001.txt new file mode 100644 index 000000000..e85cabb70 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/1001.txt @@ -0,0 +1,20 @@ +* nieuw pictogram van @FabianOvrWrt met bijdragen van @ the-eclectic-dyslexic (#517, #592) +* meer aanpasbare spatiebalk trackpad en taalschakelaar door @arcarum (#486) +* % toevoegen om de lay-out van symbolen te verschuiven (#568, #428) +* verbeter het gedrag wanneer de taalschakeltoets is ingesteld om van taal en toetsenbord te wisselen +* links naar bestaande woordenboeken weergeven bij het toevoegen van een woordenboek +* voeg Kaitag-indeling toe door @alkaitagi (#519) +* voeg Probhat layout toe door @fahimscirex (#489) +* schakel de werkbalkvolgorde voor RTL-talen optioneel om door @codokie (#557, #574) +* toestaan dat speciale lay-outs worden aangepast (numpad, telefoon, ...) + * nog steeds experimenteel, omdat de basislay-outs kunnen veranderen +* bijgewerkte spellchecker.xml om locaties op te nemen waar woordenboeken beschikbaar zijn, maar niet inbegrepen in de app +* vertalingen bijwerken (dankzij alle vertalers!) +* upgrade ndk door @Syphyr (#560) +* upgrade inline autofill-code door @arcarum (#595) +* probleem oplossen met het dialoogvenster met de werkbalktoets (#505) +* probleem oplossen met Turkse lay-out (#508) +* fixeer verkeerde schakelaarstatus bij rotatie in kleurenscherm aanpassen (#563) +* probleem oplossen met recente emoji's die niet worden geladen (#527) +* probleem oplossen met cijfers die niet in bepaalde velden verschijnen (#585) +* enkele kleine oplossingen diff --git a/fastlane/metadata/android/nl-NL/changelogs/1003.txt b/fastlane/metadata/android/nl-NL/changelogs/1003.txt new file mode 100644 index 000000000..72ac2e19f --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/1003.txt @@ -0,0 +1,9 @@ +* wijzig pictogrammen voor autocorrectie en selecteer alle werkbalktoetsen door @codokie (#524, #651) +* Chuvash-indeling toegevoegd door @tenextractor (#677) +* toets werkbalk knippen toegevoegd door @codokie (#678) +* update Probhat layout door @fahimscirex (#628) +* werkbalkpictogrammen weergeven in het dialoogvenster met werkbalktoetsen +* knop sluiten toegevoegd in de geschiedenis van het klembord door @codokie (#403, #649) +* Russische (Student) layout toegevoegd door @ Zolax9 (#640) +* maak numpad op symbolen toets lange druk optioneel (#588) +* kleine oplossingen en verbeteringen, waaronder #632, #637, #638 door @RHJihan diff --git a/fastlane/metadata/android/nl-NL/changelogs/1004.txt b/fastlane/metadata/android/nl-NL/changelogs/1004.txt new file mode 100644 index 000000000..3b0736ae0 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/1004.txt @@ -0,0 +1,7 @@ +- update Servische Cyrillische lay-out door @markokocic (#704, #705) +- update Estland layout door @tenextractor (#693) +- repareer dubbele items in de geschiedenis van het klembord door @codokie (#616, #680) +- voeg alleen tekstinvoer toe aan de geschiedenis van het klembord door @codokie (#711) +- betere afbeeldingen in metadata door @RHJihan (#713) +- correcte instelling van de kleur van pictogrammen in het dialoogvenster voor werkbalkselectie door @codokie (#715, #716) +- andere oplossingen (#684, #723 en meer) diff --git a/fastlane/metadata/android/nl-NL/changelogs/2000.txt b/fastlane/metadata/android/nl-NL/changelogs/2000.txt new file mode 100644 index 000000000..6de50113e --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/2000.txt @@ -0,0 +1,10 @@ +* voeg basisondersteuning toe voor modificatietoetsen +* voeg lange persfuncties toe aan meer werkbalktoetsen +* en meer werkbalktoetsen met klembordgeschiedenis +* maak de werkbalk van de klembordgeschiedenis aanpasbaar +* maken het mogelijk om alle kleuren aan te passen +* voeg instelling toe om altijd te laten zien dat woord moet worden ingevoerd als middelste suggestie +* voeg caps lock-indicator toe +* voeg Piemontese, Eastern Mari, Mansi, uitgebreide lay-outs toe voor Kannada en Hongaars +* repareer afgesneden tekst in de pop-up van sleutelvoorvertoningen op sommige apparaten +* verdere oplossingen en verbeteringen, zie release notes diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt new file mode 100644 index 000000000..b4020441d --- /dev/null +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -0,0 +1,29 @@ +HeliBoard is een privacybewust open-source toetsenbord, gebaseerd op AOSP / OpenBoard. +Het vraagt geen netwerkrechten en is dus 100% offline. + +Kenmerken: +
    +
  • Woordenboeken toevoegen voor suggesties en spellingcontrole
  • +
      +
    • bouw je eigen, of download ze hier, of vanuit de sectie experimenteel (kwaliteit kan variëren)
    • +
    • aanvullende woordenboeken voor emoji's of wetenschappelijke symbolen kunnen worden gebruikt voor het geven van suggesties (vergelijkbaar met "emoji zoeken")
    • +
    • merk op dat voor Koreaanse lay-outs suggesties alleen werken met dit woordenboek , de hulpmiddelen in de woordenboek-opslagplaats kunnen geen functionele woordenboeken aanmaken
    • + +
    • Toetsenbordthema's aanpassen (stijl, kleuren en achtergrondafbeelding)
    • +
        +
      • Dag/nacht-instelling van het systeem volgen op Android 10+ (en op sommige versies van Android 9)
      • +
      • Dynamische kleuren volgen voor Android 12+
      • + +
      • Toetsenbord layouts aanpassen (alleen beschikbaar bij het uitschakelen van gebruik systeemtalen )
      • +
      • Meertalige invoer
      • +
      • Vegend typen (Glide typing) ( alleen m.b.v. gesloten bronbibliotheek ☹️)
      • +
          +
        • bibliotheek niet opgenomen in de app, omdat er geen compatibele open source bibliotheek beschikbaar is
        • +
        • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier gedownload
        • + +
        • Klembordgeschiedenis
        • +
        • Enkele handmodus
        • +
        • Gesplitst toetsenbord (alleen beschikbaar als het scherm groot genoeg is)
        • +
        • Numpad
        • +
        • Back-up en terugzetten van geleerde woorden / geschiedenisgegevens
        • + diff --git a/fastlane/metadata/android/nl-NL/short_description.txt b/fastlane/metadata/android/nl-NL/short_description.txt new file mode 100644 index 000000000..1452cd12a --- /dev/null +++ b/fastlane/metadata/android/nl-NL/short_description.txt @@ -0,0 +1 @@ +Aanpasbaar open-source toetsenbord diff --git a/fastlane/metadata/android/nl-NL/title.txt b/fastlane/metadata/android/nl-NL/title.txt new file mode 100644 index 000000000..e9841ace0 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/title.txt @@ -0,0 +1 @@ +HeliBoard diff --git a/fastlane/metadata/android/pl-PL/changelogs/2000.txt b/fastlane/metadata/android/pl-PL/changelogs/2000.txt new file mode 100644 index 000000000..fd89410ba --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/2000.txt @@ -0,0 +1,10 @@ +* dodano podstawową obsługę klawiszy modyfikujących +* dodano funkcję długiego naciśnięcia dla większej liczby klawiszy paska narzędzi +* oraz więcej klawiszy paska narzędzi w schowku +* możliwość dostosowania klawiszy paska narzędzi w schowku +* możliwość dostosowania wszystkich kolorów +* dodano ustawienie, aby zawsze wyświetlać wpisywane słowo jako środkową sugestię +* dodano wskaźnik Caps Lock +* dodano piemoncki, mari wschodni, mansyjski oraz rozszerzone układy dla języka kanadyjskiego i węgierskiego +* naprawiono obcięty tekst w wyskakującym okienku podczas podglądu klawiszy na niektórych urządzeniach +* więcej poprawek i ulepszeń, zobacz informacje o wydaniu From 1b5ed600174f359dbb2a252bde900366e4635b51 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 2 Jun 2024 15:30:57 +0200 Subject: [PATCH 10/92] add changelog and adjust version name --- app/build.gradle | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- fastlane/metadata/android/en-US/changelogs/2001.txt | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/2001.txt diff --git a/app/build.gradle b/app/build.gradle index c270024d9..5ddb7bb7b 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,7 +11,7 @@ android { minSdkVersion 21 targetSdkVersion 34 versionCode 2001 - versionName '2.0-alpha1' + versionName '2.0-beta1' ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d19c1f323..c40d5ecac 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -286,7 +286,7 @@ Bestaande lay-out kopiëren Fout bij lay-out: %s Tik om lay-out te bewerken - Lay-outs (behalve symbolen) geven interne instellingen weer die mogelijk nog veranderen. Als dat gebeurt, werkt de aangepaste lay-out mogelijk niet meer goed. + Meer symbolen Telefoon Woordenboeken diff --git a/fastlane/metadata/android/en-US/changelogs/2001.txt b/fastlane/metadata/android/en-US/changelogs/2001.txt new file mode 100644 index 000000000..65c72100a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/2001.txt @@ -0,0 +1,11 @@ +* allow customizing functional key layouts +* slightly adjust symbols and more symbols layouts +* add options to auto-show/hide toolbar +* add toast notification when copying text +* separate language switch key behavior from enablement +* add comma key popups for number and phone layouts +* make long-press pinning in toolbar optional +* move toolbar settings to a separate section +* add tab key +* understand ctrl, toolbar and other key labels in layouts +* minor fixes and improvements From c212d9e551950f3ec57c078e999b6ab3231c8dd3 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 2 Jun 2024 17:55:28 +0200 Subject: [PATCH 11/92] clarify that json layout contributions should only use key type and code if necessary --- layouts.md | 1 + 1 file changed, 1 insertion(+) diff --git a/layouts.md b/layouts.md index efeb1768f..28bbfd596 100644 --- a/layouts.md +++ b/layouts.md @@ -103,6 +103,7 @@ You can also specify special key codes like `a|!code/key_action_previous`, but i ## Adding new layouts / languages * You need a layout file in one of the formats above, and add it to [layouts](app/src/main/assets/layouts) * Popup keys in the layout will be in the "_Layout_" popup key group. + * If you add a json layout, only add key type (`$`) and `code` if necessary * Add a layout entry to [`method.xml`](app/src/main/res/xml/method.xml) * `KeyboardLayoutSet` in `android:imeSubtypeExtraValue` must be set to the name of your layout file (without file ending) * `android:subtypeId` must be set to a value that is unique in this file (please use the same length as for other layouts) From 1ea8d936a4ca849ddb4c3f2d443e59dd6f1e848f Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 3 Jun 2024 19:07:39 +0200 Subject: [PATCH 12/92] upgrade dependencies --- app/build.gradle | 9 ++++---- .../settings/AppearanceSettingsFragment.kt | 5 +++-- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 14 ++++++------ gradlew.bat | 20 +++++++++--------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5ddb7bb7b..da228679a 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,9 +52,6 @@ android { } } - lintOptions { - abortOnError true - } ndkVersion '26.2.11394342' @@ -87,11 +84,15 @@ android { } namespace "helium314.keyboard.latin" + lint { + abortOnError true + } } dependencies { // androidx implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.autofill:autofill:1.1.0' @@ -99,8 +100,6 @@ dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" - // when kotlin-serialization is enabled, Android Studio stops complaining about "Unresolved reference: serializer", but the build fails -// implementation "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // color picker for user-defined colors implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0' diff --git a/app/src/main/java/helium314/keyboard/latin/settings/AppearanceSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/AppearanceSettingsFragment.kt index d372633e0..df1969b2a 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AppearanceSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AppearanceSettingsFragment.kt @@ -156,8 +156,9 @@ class AppearanceSettingsFragment : SubScreenFragment() { summary = entries[entryValues.indexOfFirst { it == value }] } dayNightPref?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value -> - colorsNightPref?.isVisible = value as Boolean - userColorsPrefNight?.isVisible = value && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT + val yesThisIsBoolean = value as Boolean // apparently kotlin smartcast got less smart with 2.0.0 + colorsNightPref?.isVisible = yesThisIsBoolean + userColorsPrefNight?.isVisible = yesThisIsBoolean && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT true } colorsNightPref?.isVisible = dayNightPref?.isChecked == true diff --git a/build.gradle b/build.gradle index 83e31cf34..babb04ad2 100755 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.9.24' + ext.kotlin_version = '2.0.0' repositories { mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.4' // 8.2 requires newer Android Studio that is even worse than 2022.x in performance + classpath 'com.android.tools.build:gradle:8.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100755 GIT binary patch literal 43462 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 46671acb6..8a1f6b97f 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 4de8f770efe05cb8c598bd69f795673e0273325a Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 6 Jun 2024 18:27:08 +0200 Subject: [PATCH 13/92] update description --- README.md | 3 ++- app/src/main/res/values-nl/strings.xml | 1 - fastlane/metadata/android/en-US/full_description.txt | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 391e590b5..bcbfa65f4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Does not use internet permission, and thus is 100% offline.

        • can follow dynamic colors for Android 12+
      • Customize keyboard layouts (only available when disabling use system languages)
      • +
      • Customize special layouts, like symbols, number, or functional key layout
      • Multilingual typing
      • Glide typing (only with closed source library ☹️)
        • @@ -44,7 +45,7 @@ Does not use internet permission, and thus is 100% offline.
        • One-handed mode
        • Split keyboard (only available if the screen is large enough)
        • Number pad
        • -
        • Backup and restore your learned word / history data
        • +
        • Backup and restore your settings and learned word / history data
        ## FAQ / Common Issues diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c40d5ecac..a6f32b839 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -286,7 +286,6 @@ Bestaande lay-out kopiëren Fout bij lay-out: %s Tik om lay-out te bewerken - Meer symbolen Telefoon Woordenboeken diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index dcb0ab318..953c1e15a 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -15,15 +15,16 @@ Features:
      • can follow dynamic colors for Android 12+
    • Customize keyboard layouts (only available when disabling use system languages)
    • +
    • Customize special layouts, like symbols, number, or functional key layout
    • Multilingual typing
    • Glide typing (only with closed source library ☹️)
      • library not included in the app, as there is no compatible open source library available
      • -
      • can be extracted from GApps packages ("swypelibs"), or downloaded here
      • +
      • can be extracted from GApps packages ("swypelibs"), or downloaded here (click on the file and then "raw" or the tiny download button)
    • Clipboard history
    • One-handed mode
    • Split keyboard (only available if the screen is large enough)
    • Number pad
    • -
    • Backup and restore your learned word / history data
    • +
    • Backup and restore your settings and learned word / history data
    From bdc418cb7097690cd0114bb495fd951af96df5f1 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 6 Jun 2024 21:53:23 +0200 Subject: [PATCH 14/92] improve popup key compatibility woith codes and labels --- .../keyboard/internal/KeyboardCodesSet.java | 8 +++++++- .../keyboard_parser/floris/KeyData.kt | 8 -------- .../keyboard_parser/floris/TextKeyData.kt | 20 +++++++++++++++++++ .../helium314/keyboard/KeyboardParserTest.kt | 19 +++++++++++++++--- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java index c089b2b62..4dc91203a 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java @@ -22,7 +22,13 @@ private KeyboardCodesSet() { public static int getCode(final String name) { Integer id = sNameToIdMap.get(name); - if (id == null) throw new RuntimeException("Unknown key code: " + name); + if (id == null) { + try { + return KeyCode.INSTANCE.checkAndConvertCode(Integer.parseInt(name)); + } catch (final Exception e) { + throw new RuntimeException("Unknown key code: " + name); + } + } return DEFAULT[id]; } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt index 7c1897df7..c9d53acbf 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt @@ -45,14 +45,6 @@ interface AbstractKeyData { * it is always required to check for the string's length before attempting to directly retrieve the first char. */ fun asString(isForDisplay: Boolean): String // todo: remove it? not used at all (better only later, maybe useful for getting display label in some languages) - - /** get the label, but also considers code, which can't be set separately for popup keys and thus goes into the label */ - fun getPopupLabel(params: KeyboardParams): String { - val keyData = if (this is KeyData) this else compute(params) ?: return "" - if (keyData.code == KeyCode.UNSPECIFIED || keyData.code < 0) // don't allow negative codes in popups - return keyData.label - return "${keyData.label}|${StringUtils.newSingleCodePointString(keyData.code)}" - } } /** diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index ac64c5178..00b0e2465 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -289,6 +289,26 @@ sealed interface KeyData : AbstractKeyData { private const val POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS_NEXT = "!fixedColumnOrder!4,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next" } + /** get the label, but also considers code, which can't be set separately for popup keys and thus goes into the label */ + // this mashes the code into the popup label to make it work + // actually that's a bad approach, but at the same time doing things properly and with reasonable performance requires much more work + // so better only do it in case the popup stuff needs more improvements + // idea: directly create PopupKeySpec, but need to deal with needsToUpcase and popupKeysColumnAndFlags + fun getPopupLabel(params: KeyboardParams): String { + val newLabel = processLabel(params) + if (code == KeyCode.UNSPECIFIED) { + return if (newLabel == label) label + else if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys + else "${newLabel}|!code/${processCode()}" + } + if (code >= 32) + return "${newLabel}|${StringUtils.newSingleCodePointString(code)}" + if (code in KeyCode.Spec.CURRENCY) + return newLabel // should be treated correctly here, currently this is done in PopupKeysUtils + return if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys + else "${newLabel}|!code/${processCode()}" + } + override fun compute(params: KeyboardParams): KeyData? { require(groupId <= GROUP_ENTER) { "only groups up to GROUP_ENTER are supported" } require(label.isNotEmpty() || type == KeyType.PLACEHOLDER || code != KeyCode.UNSPECIFIED) { "non-placeholder key has no code and no label" } diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 4715ee577..958ea8c97 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -256,9 +256,15 @@ f""", // no newline at the end { "label": "m", "popup": { "main": { "label": "/" } } } ], [ - { "label": "w" }, - { "label": "x" }, - { "label": "c" }, + { "label": "w", "popup": { + "main": { "code": 55, "label": "!" } + } }, + { "label": "x", "popup": { + "main": { "label": "undo" } + } }, + { "label": "c", "popup": { + "main": { "code": -10001, "label": "x" } + } }, { "label": "v" }, { "label": "b" }, { "label": "n" } @@ -275,6 +281,13 @@ f""", // no newline at the end assertEquals(expected[index].popups?.sorted(), keyParams.mPopupKeys?.mapNotNull { it.mLabel }?.sorted()) // todo (later): what's wrong with order? assertEquals(expected[index].text, keyParams.outputText) } + assertEquals("!", keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals('7'.code, keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals(null, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("undo", keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(KeyCode.UNDO, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals("x", keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals(-10001, keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mCode) } @Test fun canLoadKeyboard() { From a841777b921ba769cf8bb5a1b8d5839f08536579 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 6 Jun 2024 22:18:53 +0200 Subject: [PATCH 15/92] improve currency popup handling --- .../keyboard_parser/floris/TextKeyData.kt | 33 +++++++++++-------- .../helium314/keyboard/KeyboardParserTest.kt | 5 +-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 00b0e2465..76b876ad1 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -303,12 +303,27 @@ sealed interface KeyData : AbstractKeyData { } if (code >= 32) return "${newLabel}|${StringUtils.newSingleCodePointString(code)}" - if (code in KeyCode.Spec.CURRENCY) - return newLabel // should be treated correctly here, currently this is done in PopupKeysUtils + if (code in KeyCode.Spec.CURRENCY) { + return getCurrencyLabel(params) + } return if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys else "${newLabel}|!code/${processCode()}" } + fun getCurrencyLabel(params: KeyboardParams): String { + val newLabel = processLabel(params) + return when (code) { + // consider currency codes for label + KeyCode.CURRENCY_SLOT_1 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.first}" + KeyCode.CURRENCY_SLOT_2 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.second[0]}" + KeyCode.CURRENCY_SLOT_3 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.second[1]}" + KeyCode.CURRENCY_SLOT_4 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.second[2]}" + KeyCode.CURRENCY_SLOT_5 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.second[3]}" + KeyCode.CURRENCY_SLOT_6 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.second[4]}" + else -> throw IllegalStateException("code in currency range, but not in currency range?") + } + } + override fun compute(params: KeyboardParams): KeyData? { require(groupId <= GROUP_ENTER) { "only groups up to GROUP_ENTER are supported" } require(label.isNotEmpty() || type == KeyType.PLACEHOLDER || code != KeyCode.UNSPECIFIED) { "non-placeholder key has no code and no label" } @@ -350,19 +365,9 @@ sealed interface KeyData : AbstractKeyData { val newLabel: String if (code in KeyCode.Spec.CURRENCY) { // special treatment necessary, because we may need to encode it in the label - // (currency is a string, so might have more than 1 codepoint) + // (currency is a string, so might have more than 1 codepoint, e.g. for Nepal) newCode = 0 - val l = processLabel(params) - newLabel = when (code) { - // consider currency codes for label - KeyCode.CURRENCY_SLOT_1 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.first}" - KeyCode.CURRENCY_SLOT_2 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[0]}" - KeyCode.CURRENCY_SLOT_3 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[1]}" - KeyCode.CURRENCY_SLOT_4 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[2]}" - KeyCode.CURRENCY_SLOT_5 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[3]}" - KeyCode.CURRENCY_SLOT_6 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[4]}" - else -> throw IllegalStateException("code in currency range, but not in currency range?") - } + newLabel = getCurrencyLabel(params) } else { newCode = processCode() newLabel = processLabel(params) diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 958ea8c97..9af660067 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -145,7 +145,7 @@ f""", // no newline at the end Expected("?123", "?123", -202, null), Expected(null, null, ' '.code, null), Expected("(", null, '('.code, listOf("<", "[", "{")), - Expected("$", null, '$'.code, listOf("£", "₱", "€", "¢", "¥")), + Expected("$", null, '$'.code, listOf("£", "₱", "€", "¢", "¥", "¥")), Expected("a", null, ' '.code, null), Expected("a", null, ' '.code, null), Expected(null, null, KeyCode.CLIPBOARD, null), // todo: expect an icon @@ -234,7 +234,8 @@ f""", // no newline at the end { "code": -806, "label": "currency_slot_6" }, { "code": -803, "label": "currency_slot_3" }, { "code": -804, "label": "currency_slot_4" }, - { "code": -805, "label": "currency_slot_5" } + { "code": -805, "label": "currency_slot_5" }, + { "code": -804, "label": "$$$4" } ] } }, { "code": 32, "label": "a|!code/key_delete" }, From d11237ba23a73ffc0a4a1642b9b24fdf9d264e6a Mon Sep 17 00:00:00 2001 From: codokie <151087174+codokie@users.noreply.github.com> Date: Fri, 7 Jun 2024 09:21:13 +0300 Subject: [PATCH 16/92] add emoji toolbar key (#845) --- .../java/helium314/keyboard/latin/utils/ToolbarUtils.kt | 6 ++++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index 8c7ba0bec..76e9b1a32 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -59,6 +59,7 @@ fun getCodeForToolbarKey(key: ToolbarKey) = when (key) { SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_WORD CLEAR_CLIPBOARD -> KeyCode.CLIPBOARD_CLEAR_HISTORY CLOSE_HISTORY -> KeyCode.ALPHA + EMOJI -> KeyCode.EMOJI } fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) { @@ -95,6 +96,7 @@ fun getStyleableIconId(key: ToolbarKey) = when (key) { FULL_RIGHT -> R.styleable.Keyboard_iconFullRight SELECT_WORD -> R.styleable.Keyboard_iconSelectWord CLOSE_HISTORY -> R.styleable.Keyboard_iconClose + EMOJI -> R.styleable.Keyboard_iconEmojiNormalKey } fun getToolbarIconByName(name: String, context: Context): Drawable? { @@ -109,14 +111,14 @@ fun getToolbarIconByName(name: String, context: Context): Drawable? { // names need to be aligned with resources strings (using lowercase of key.name) enum class ToolbarKey { VOICE, CLIPBOARD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, CUT, ONE_HANDED, LEFT, RIGHT, UP, DOWN, - FULL_LEFT, FULL_RIGHT, INCOGNITO, AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY + FULL_LEFT, FULL_RIGHT, INCOGNITO, AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY, EMOJI } val toolbarKeyStrings: Set = entries.mapTo(HashSet()) { it.toString().lowercase(Locale.US) } val defaultToolbarPref = entries.filterNot { it == CLOSE_HISTORY }.joinToString(";") { when (it) { - INCOGNITO, AUTOCORRECT, UP, DOWN, ONE_HANDED, FULL_LEFT, FULL_RIGHT, CUT, CLEAR_CLIPBOARD -> "${it.name},false" + INCOGNITO, AUTOCORRECT, UP, DOWN, ONE_HANDED, FULL_LEFT, FULL_RIGHT, CUT, CLEAR_CLIPBOARD, EMOJI -> "${it.name},false" else -> "${it.name},true" } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb146b91f..fe8f3f71c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -252,6 +252,7 @@ Undo Redo Close clipboard history + Emoji Select clipboard toolbar keys From a297b6037c955506e364cdc5907da7dfaa6d4d28 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 8 Jun 2024 11:31:40 +0200 Subject: [PATCH 17/92] fix missing preview popups for keys with icon and negative code --- .../main/java/helium314/keyboard/keyboard/Key.java | 11 ++++++++--- .../keyboard/keyboard/internal/KeySpecParser.java | 2 +- .../internal/keyboard_parser/floris/TextKeyData.kt | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index c288fefb9..3f8e2b8a1 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -921,6 +921,8 @@ public final Drawable selectBackgroundDrawable(@NonNull final Drawable keyBackgr public final boolean isAccentColored() { if (hasActionKeyBackground()) return true; final String iconName = getIconName(); + // todo: other way of identifying the color? + // if yes, NAME_CLIPBOARD_ACTION_KEY and NAME_CLIPBOARD_NORMAL_KEY could be merged return iconName.equals(KeyboardIconsSet.NAME_NEXT_KEY) || iconName.equals(KeyboardIconsSet.NAME_PREVIOUS_KEY) || iconName.equals(KeyboardIconsSet.NAME_CLIPBOARD_ACTION_KEY) @@ -1149,12 +1151,15 @@ public KeyParams( || (mCode == KeyCode.SYMBOL_ALPHA && !params.mId.isAlphabetKeyboard()) ) actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; - if (mCode <= Constants.CODE_SPACE && mCode != KeyCode.MULTIPLE_CODE_POINTS) + if (mCode <= Constants.CODE_SPACE && mCode != KeyCode.MULTIPLE_CODE_POINTS && mIconName.equals(KeyboardIconsSet.NAME_UNDEFINED)) actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; + switch (mCode) { + case KeyCode.DELETE, KeyCode.SHIFT, Constants.CODE_ENTER, KeyCode.SHIFT_ENTER, KeyCode.ALPHA, Constants.CODE_SPACE, + KeyCode.SYMBOL, KeyCode.SYMBOL_ALPHA -> actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; // no preview even if icon! + case KeyCode.SETTINGS, KeyCode.LANGUAGE_SWITCH -> actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING; + } if (mCode == KeyCode.DELETE) actionFlags |= ACTION_FLAGS_IS_REPEATABLE; - if (mCode == KeyCode.SETTINGS || mCode == KeyCode.LANGUAGE_SWITCH) - actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING; mActionFlags = actionFlags; final int altCodeInAttr; // settings and language switch keys have alt code space, all others nothing diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java index 1c2ba1074..3463ed451 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java @@ -221,7 +221,7 @@ public static int parseCode(@Nullable final String text, final int defaultCode) return defaultCode; } - @NonNull + @NonNull // todo: why not null instead of NAME_UNDEFINED? public static String getIconName(@Nullable final String keySpec) { if (keySpec == null) { // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 76b876ad1..bf2827dff 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -113,6 +113,7 @@ sealed interface KeyData : AbstractKeyData { else "!icon/space_key_for_number_layout|!code/key_space" // todo: emoji and language switch popups should actually disappear depending on current layout (including functional keys) + // keys could be replaced with toolbar keys, but parsing needs to be adjusted (should happen anyway...) private fun getCommaPopupKeys(params: KeyboardParams): List { val keys = mutableListOf() if (!params.mId.mDeviceLocked) From 743874f74c677cff1f3ea6267ea4506f2b4fb65a Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 8 Jun 2024 11:47:42 +0200 Subject: [PATCH 18/92] remove icon name "undefined" and use null instead --- .../java/helium314/keyboard/keyboard/Key.java | 43 ++++++++++--------- .../keyboard/internal/KeyPreviewView.java | 2 +- .../keyboard/internal/KeySpecParser.java | 6 +-- .../keyboard/internal/KeyboardIconsSet.kt | 3 +- .../keyboard/internal/PopupKeySpec.java | 9 ++-- .../latin/suggestions/MoreSuggestions.java | 2 +- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 3f8e2b8a1..1877b02f8 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -78,7 +78,7 @@ public class Key implements Comparable { public static final int LABEL_FLAGS_DISABLE_ADDITIONAL_POPUP_KEYS = 0x80000000; /** Icon to display instead of a label. Icon takes precedence over a label */ - @NonNull private final String mIconName; + @Nullable private final String mIconName; /** Width of the key, excluding the gap */ private final int mWidth; @@ -154,13 +154,13 @@ private static final class OptionalAttributes { public final String mOutputText; public final int mAltCode; /** Icon for disabled state */ - public final String mDisabledIconName; + @Nullable public final String mDisabledIconName; /** The visual insets */ public final int mVisualInsetsLeft; public final int mVisualInsetsRight; - private OptionalAttributes(final String outputText, final int altCode, - final String disabledIconName, final int visualInsetsLeft, final int visualInsetsRight) { + private OptionalAttributes(final String outputText, final int altCode, @Nullable final String disabledIconName, + final int visualInsetsLeft, final int visualInsetsRight) { mOutputText = outputText; mAltCode = altCode; mDisabledIconName = disabledIconName; @@ -170,9 +170,9 @@ private OptionalAttributes(final String outputText, final int altCode, @Nullable public static OptionalAttributes newInstance(final String outputText, final int altCode, - final String disabledIconName, final int visualInsetsLeft, final int visualInsetsRight) { + @Nullable final String disabledIconName, final int visualInsetsLeft, final int visualInsetsRight) { if (outputText == null && altCode == KeyCode.NOT_SPECIFIED - && disabledIconName.equals(KeyboardIconsSet.NAME_UNDEFINED) && visualInsetsLeft == 0 + && disabledIconName == null && visualInsetsLeft == 0 && visualInsetsRight == 0) { return null; } @@ -191,7 +191,7 @@ public static OptionalAttributes newInstance(final String outputText, final int /** * Constructor for a key on PopupKeyKeyboard and on MoreSuggestions. */ - public Key(@Nullable final String label, @NonNull final String iconName, final int code, + public Key(@Nullable final String label, @Nullable final String iconName, final int code, @Nullable final String outputText, @Nullable final String hintLabel, final int labelFlags, final int backgroundType, final int x, final int y, final int width, final int height, final int horizontalGap, final int verticalGap) { @@ -207,8 +207,7 @@ public Key(@Nullable final String label, @NonNull final String iconName, final i mPopupKeys = null; mPopupKeysColumnAndFlags = 0; mLabel = label; - mOptionalAttributes = OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, - KeyboardIconsSet.NAME_UNDEFINED, 0, 0); + mOptionalAttributes = OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, null, 0, 0); mCode = code; mEnabled = (code != KeyCode.NOT_SPECIFIED); mIconName = iconName; @@ -278,7 +277,7 @@ public Key(@NonNull final Key key, @Nullable final PopupKeySpec[] popupKeys, mActionFlags = key.mActionFlags; mKeyVisualAttributes = key.mKeyVisualAttributes; mOptionalAttributes = outputText == null ? null - : Key.OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, KeyboardIconsSet.NAME_UNDEFINED, 0, 0); + : Key.OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, null, 0, 0); mHashCode = key.mHashCode; // Key state. mPressed = key.mPressed; @@ -404,7 +403,7 @@ private boolean equalsInternal(final Key o) { && o.mCode == mCode && TextUtils.equals(o.mLabel, mLabel) && TextUtils.equals(o.mHintLabel, mHintLabel) - && o.mIconName.equals(mIconName) + && TextUtils.equals(o.mIconName, mIconName) && o.mBackgroundType == mBackgroundType && Arrays.equals(o.mPopupKeys, mPopupKeys) && TextUtils.equals(o.getOutputText(), getOutputText()) @@ -445,7 +444,7 @@ public String toShortString() { public String toLongString() { final String iconName = getIconName(); - final String topVisual = (iconName.equals(KeyboardIconsSet.NAME_UNDEFINED)) + final String topVisual = (iconName != null) ? KeyboardIconsSet.PREFIX_ICON + iconName : getLabel(); final String hintLabel = getHintLabel(); final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; @@ -703,6 +702,7 @@ public final int getAltCode() { return (attrs != null) ? attrs.mAltCode : KeyCode.NOT_SPECIFIED; } + @Nullable public String getIconName() { return mIconName; } @@ -710,7 +710,7 @@ public String getIconName() { @Nullable public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { final OptionalAttributes attrs = mOptionalAttributes; - final String iconName = mEnabled ? getIconName() : ((attrs != null) ? attrs.mDisabledIconName : KeyboardIconsSet.NAME_UNDEFINED); + final String iconName = mEnabled ? getIconName() : ((attrs != null) ? attrs.mDisabledIconName : null); final Drawable icon = iconSet.getIconDrawable(iconName); if (icon != null) { icon.setAlpha(alpha); @@ -921,6 +921,7 @@ public final Drawable selectBackgroundDrawable(@NonNull final Drawable keyBackgr public final boolean isAccentColored() { if (hasActionKeyBackground()) return true; final String iconName = getIconName(); + if (iconName == null) return false; // todo: other way of identifying the color? // if yes, NAME_CLIPBOARD_ACTION_KEY and NAME_CLIPBOARD_NORMAL_KEY could be merged return iconName.equals(KeyboardIconsSet.NAME_NEXT_KEY) @@ -945,7 +946,7 @@ private Spacer(KeyParams keyParams) { */ protected Spacer(final KeyboardParams params, final int x, final int y, final int width, final int height) { - super(null, KeyboardIconsSet.NAME_UNDEFINED, KeyCode.NOT_SPECIFIED, null, + super(null, null, KeyCode.NOT_SPECIFIED, null, null, 0, BACKGROUND_TYPE_EMPTY, x, y, width, height, params.mHorizontalGap, params.mVerticalGap); } @@ -970,7 +971,7 @@ public static class KeyParams { @Nullable public String mLabel; @Nullable public final String mHintLabel; public final int mLabelFlags; - @NonNull public final String mIconName; + @Nullable public final String mIconName; @Nullable public PopupKeySpec[] mPopupKeys; public final int mPopupKeysColumnAndFlags; public int mBackgroundType; @@ -1151,10 +1152,10 @@ public KeyParams( || (mCode == KeyCode.SYMBOL_ALPHA && !params.mId.isAlphabetKeyboard()) ) actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; - if (mCode <= Constants.CODE_SPACE && mCode != KeyCode.MULTIPLE_CODE_POINTS && mIconName.equals(KeyboardIconsSet.NAME_UNDEFINED)) + if (mCode <= Constants.CODE_SPACE && mCode != KeyCode.MULTIPLE_CODE_POINTS && mIconName == null) actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; switch (mCode) { - case KeyCode.DELETE, KeyCode.SHIFT, Constants.CODE_ENTER, KeyCode.SHIFT_ENTER, KeyCode.ALPHA, Constants.CODE_SPACE, + case KeyCode.DELETE, KeyCode.SHIFT, Constants.CODE_ENTER, KeyCode.SHIFT_ENTER, KeyCode.ALPHA, Constants.CODE_SPACE, KeyCode.NUMPAD, KeyCode.SYMBOL, KeyCode.SYMBOL_ALPHA -> actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; // no preview even if icon! case KeyCode.SETTINGS, KeyCode.LANGUAGE_SWITCH -> actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING; } @@ -1172,7 +1173,7 @@ public KeyParams( : altCodeInAttr; mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, // disabled icon only ever for old version of shortcut key, visual insets can be replaced with spacer - KeyboardIconsSet.NAME_UNDEFINED, 0, 0); + null, 0, 0); // KeyVisualAttributes for a key essentially are what the theme has, but on a per-key base // could be used e.g. for having a color gradient on key color mKeyVisualAttributes = null; @@ -1212,11 +1213,11 @@ public KeyParams(@Nullable final String label, final int code, @Nullable final S mLabel = label; mOptionalAttributes = code == KeyCode.MULTIPLE_CODE_POINTS - ? OptionalAttributes.newInstance(label, KeyCode.NOT_SPECIFIED, KeyboardIconsSet.NAME_UNDEFINED, 0, 0) + ? OptionalAttributes.newInstance(label, KeyCode.NOT_SPECIFIED, null, 0, 0) : null; mCode = code; mEnabled = (code != KeyCode.NOT_SPECIFIED); - mIconName = KeyboardIconsSet.NAME_UNDEFINED; + mIconName = null; mKeyVisualAttributes = null; } @@ -1230,7 +1231,7 @@ private KeyParams(final KeyboardParams params) { mHintLabel = null; mKeyVisualAttributes = null; mOptionalAttributes = null; - mIconName = KeyboardIconsSet.NAME_UNDEFINED; + mIconName = null; mBackgroundType = BACKGROUND_TYPE_NORMAL; mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; mPopupKeys = null; diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java index 98b646f16..e20e9fb5e 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java @@ -45,7 +45,7 @@ public KeyPreviewView(final Context context, final AttributeSet attrs, final int public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams) { // What we show as preview should match what we show on a key top in onDraw(). - if (!key.getIconName().equals(KeyboardIconsSet.NAME_UNDEFINED)) { + if (key.getIconName() != null) { setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet)); setText(null); return; diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java index 3463ed451..aa5c8ae68 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java @@ -221,14 +221,14 @@ public static int parseCode(@Nullable final String text, final int defaultCode) return defaultCode; } - @NonNull // todo: why not null instead of NAME_UNDEFINED? + @Nullable public static String getIconName(@Nullable final String keySpec) { if (keySpec == null) { // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. - return KeyboardIconsSet.NAME_UNDEFINED; + return null; } if (!hasIcon(keySpec)) { - return KeyboardIconsSet.NAME_UNDEFINED; + return null; } final int labelEnd = indexOfLabelEnd(keySpec); return getBeforeLabelEnd(keySpec, labelEnd).substring(KeyboardIconsSet.PREFIX_ICON.length()).intern(); diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt index 9c10953e8..0221085c9 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt @@ -24,13 +24,12 @@ class KeyboardIconsSet { } } - fun getIconDrawable(name: String) = iconsByName[name] + fun getIconDrawable(name: String?) = iconsByName[name] companion object { private val TAG = KeyboardIconsSet::class.simpleName const val PREFIX_ICON = "!icon/" - const val NAME_UNDEFINED = "undefined" const val NAME_SHIFT_KEY = "shift_key" const val NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted" const val NAME_SHIFT_KEY_LOCKED = "shift_key_locked" diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java b/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java index 946dc5ba6..21f862673 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java @@ -40,7 +40,7 @@ public final class PopupKeySpec { public final String mLabel; @Nullable public final String mOutputText; - @NonNull + @Nullable public final String mIconName; public PopupKeySpec(@NonNull final String popupKeySpec, boolean needsToUpperCase, @@ -78,7 +78,8 @@ public Key buildKey(final int x, final int y, final int labelFlags, @Override public int hashCode() { int hashCode = 31 + mCode; - hashCode = hashCode * 31 + mIconName.hashCode(); + final String iconName = mIconName; + hashCode = hashCode * 31 + (iconName == null ? 0 : iconName.hashCode()); final String label = mLabel; hashCode = hashCode * 31 + (label == null ? 0 : label.hashCode()); final String outputText = mOutputText; @@ -94,7 +95,7 @@ public boolean equals(final Object o) { if (o instanceof PopupKeySpec) { final PopupKeySpec other = (PopupKeySpec)o; return mCode == other.mCode - && mIconName.equals(other.mIconName) + && TextUtils.equals(mIconName, other.mIconName) && TextUtils.equals(mLabel, other.mLabel) && TextUtils.equals(mOutputText, other.mOutputText); } @@ -103,7 +104,7 @@ public boolean equals(final Object o) { @Override public String toString() { - final String label = (mIconName.equals(KeyboardIconsSet.NAME_UNDEFINED) ? mLabel + final String label = (mIconName == null ? mLabel : KeyboardIconsSet.PREFIX_ICON + mIconName); final String output = (mCode == KeyCode.MULTIPLE_CODE_POINTS ? mOutputText : Constants.printableCode(mCode)); diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/MoreSuggestions.java b/app/src/main/java/helium314/keyboard/latin/suggestions/MoreSuggestions.java index 8ef29dd40..2799c1f7f 100644 --- a/app/src/main/java/helium314/keyboard/latin/suggestions/MoreSuggestions.java +++ b/app/src/main/java/helium314/keyboard/latin/suggestions/MoreSuggestions.java @@ -231,7 +231,7 @@ static final class MoreSuggestionKey extends Key { public MoreSuggestionKey(final String word, final String info, final int index, final MoreSuggestionsParam params) { - super(word, KeyboardIconsSet.NAME_UNDEFINED, KeyCode.MULTIPLE_CODE_POINTS, + super(word, null, KeyCode.MULTIPLE_CODE_POINTS, word, info, 0, Key.BACKGROUND_TYPE_NORMAL, params.getX(index), params.getY(index), params.getWidth(index), params.mDefaultAbsoluteRowHeight, params.mHorizontalGap, params.mVerticalGap); From 9c926911fc93b89ff2cd5bf351db1113c43b77af Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 9 Jun 2024 11:25:34 +0200 Subject: [PATCH 19/92] set preview popup icons as left drawable instead of bottom fixes #811 remaining issue: when pressing a key with icon and preview popup before a letter popup, preview and popup height is too low but this should only trigger rarely, and is not worse than the fixed issue --- .../helium314/keyboard/keyboard/internal/KeyPreviewView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java index e20e9fb5e..e24a240e4 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyPreviewView.java @@ -46,7 +46,7 @@ public KeyPreviewView(final Context context, final AttributeSet attrs, final int public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams) { // What we show as preview should match what we show on a key top in onDraw(). if (key.getIconName() != null) { - setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet)); + setCompoundDrawables(key.getPreviewIcon(iconsSet), null, null, null); setText(null); return; } From d596c028a119f306c5a6e0006859164dcff3e2b3 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 9 Jun 2024 12:12:38 +0200 Subject: [PATCH 20/92] more logging --- .../main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt index ccd79c7cd..add9d6d9e 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt @@ -101,7 +101,7 @@ fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { if (subtype != null) { return subtype } else { - Log.w(TAG, "selected subtype $localeAndLayout not found") + Log.w(TAG, "selected subtype $localeAndLayout / ${prefs.getString(Settings.PREF_SELECTED_SUBTYPE, "")} not found") } if (subtypes.isNotEmpty()) return subtypes.first() From ce30c3b2940921dde6ec3b102b748f3a325d4820 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 10 Jun 2024 22:53:41 +0200 Subject: [PATCH 21/92] fix issue with text being inserted when deleting quicky in joplin (and probably other apps) --- .../keyboard/latin/RichInputConnection.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index f4711f5f6..3c83028ab 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -449,6 +449,21 @@ public int getCharBeforeBeforeCursor() { final long startTime = SystemClock.uptimeMillis(); final CharSequence result = mIC.getTextBeforeCursor(n, flags); detectLaggyConnection(operation, timeout, startTime); + if (result != null && result.length() > 1 && (mComposingText.length() > 0 || mCommittedTextBeforeComposingText.length() > 0)) { + final char actualLastChar = result.charAt(result.length() - 1); + final char cachedLastChar = mComposingText.length() > 0 + ? mComposingText.charAt(mComposingText.length() - 1) + : mCommittedTextBeforeComposingText.charAt(mCommittedTextBeforeComposingText.length() - 1); + if (actualLastChar != cachedLastChar) { + Log.w(TAG, "cached text out of sync, reloading"); + ExtractedTextRequest r = new ExtractedTextRequest(); + final ExtractedText et = mIC.getExtractedText(r, 0); + mExpectedSelStart = et.selectionStart + et.startOffset; + mExpectedSelEnd = et.selectionEnd + et.startOffset; + if (!DebugLogUtils.getStackTrace(2).contains("reloadTextCache")) // clunky bur effective protection against circular reference + reloadTextCache(); + } + } return result; } From 843b8c44f737262d3e9cabbdbe904dd8d1c5b42d Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 11 Jun 2024 22:02:32 +0200 Subject: [PATCH 22/92] replace old fix from 9713c4a25a4a25784a018a991ffbf6ddcdd8927a with improved consistency check and avoid somewhat unexpected crash --- .../keyboard/latin/RichInputConnection.java | 107 ++++++++---------- .../keyboard/latin/inputlogic/InputLogic.java | 7 +- 2 files changed, 48 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 3c83028ab..2cd1e0b41 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -449,12 +449,13 @@ public int getCharBeforeBeforeCursor() { final long startTime = SystemClock.uptimeMillis(); final CharSequence result = mIC.getTextBeforeCursor(n, flags); detectLaggyConnection(operation, timeout, startTime); - if (result != null && result.length() > 1 && (mComposingText.length() > 0 || mCommittedTextBeforeComposingText.length() > 0)) { - final char actualLastChar = result.charAt(result.length() - 1); - final char cachedLastChar = mComposingText.length() > 0 - ? mComposingText.charAt(mComposingText.length() - 1) - : mCommittedTextBeforeComposingText.charAt(mCommittedTextBeforeComposingText.length() - 1); - if (actualLastChar != cachedLastChar) { + // inconsistent state can occur for (at least) two reasons + // 1. the app actively changes text field content, e.g. joplin when deleting "list markers like 2. + // 2. the app has outdated contents in the text field, e.g. notepad (com.farmerbb.notepad) returns the + // just deleted char right after deletion, instead of the correct one + // todo: understand where this inconsistent state comes from, is it really the other app's fault, or is it HeliBoard? + if (result != null) { + if (!checkTextBeforeCursorConsistency(result)) { Log.w(TAG, "cached text out of sync, reloading"); ExtractedTextRequest r = new ExtractedTextRequest(); final ExtractedText et = mIC.getExtractedText(r, 0); @@ -467,6 +468,41 @@ public int getCharBeforeBeforeCursor() { return result; } + // checks whether the end of cached text before cursor is the same as end of the given CharSequence + // this is done to find inconsistencies that arise in some text fields when characters are deleted, but also in some other cases + // only checks the end for performance reasons + // may need to check more than just the last character, because the text may end in e.g. rrrrr and even there a single car offset should be found + private boolean checkTextBeforeCursorConsistency(final CharSequence textField) { + final int lastIndex = textField.length() - 1; + if (lastIndex == -1) return true; + final char lastChar = textField.charAt(lastIndex); + final int composingLength = mComposingText.length(); + for (int i = 0; i <= lastIndex; i++) { + // get last minus i character and compare + final char currentTextFieldChar = textField.charAt(lastIndex - i); + final char currentCachedChar; + if (i < composingLength) { + // take char from composing text + currentCachedChar = mComposingText.charAt(composingLength - 1 - i); + } else { + // take last char from mCommittedTextBeforeComposingText, consider composing length + final int index = mCommittedTextBeforeComposingText.length() - 1 - (i - composingLength); + if (index < mCommittedTextBeforeComposingText.length() && index >= 0) + currentCachedChar = mCommittedTextBeforeComposingText.charAt(index); + else return lastIndex > 100; // still let it pass if the same character is repeated many times, but cached text too short + } + + if (currentTextFieldChar != currentCachedChar) + // different character -> inconsistent + return false; + + if (lastChar != currentTextFieldChar) + // not the same, and no inconsistency found so far -> unlikely there is one that won't be found later -> return early + return true; + } + return true; // no inconsistency found after going through everything + } + @Nullable public CharSequence getTextAfterCursor(final int n, final int flags) { return getTextAfterCursorAndDetectLaggyConnection( OPERATION_GET_TEXT_AFTER_CURSOR, @@ -595,6 +631,7 @@ public void sendKeyEvent(final KeyEvent keyEvent) { public void setComposingRegion(final int start, final int end) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); + final int moveBy = mExpectedSelStart - start; // determine now, as mExpectedSelStart may change in getTextBeforeCursor final CharSequence textBeforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0); mCommittedTextBeforeComposingText.setLength(0); @@ -603,8 +640,7 @@ public void setComposingRegion(final int start, final int end) { // position in mExpectedSelStart and mExpectedSelEnd. In this case we want the start // of the text, so we should use mExpectedSelStart. In other words, the composing // text starts (mExpectedSelStart - start) characters before the end of textBeforeCursor - final int indexOfStartOfComposingText = - Math.max(textBeforeCursor.length() - (mExpectedSelStart - start), 0); + final int indexOfStartOfComposingText = Math.max(textBeforeCursor.length() - moveBy, 0); mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText, textBeforeCursor.length())); mCommittedTextBeforeComposingText.append( @@ -666,7 +702,7 @@ public void selectAll() { public void selectWord(final SpacingAndPunctuations spacingAndPunctuations, final String script) { if (!isConnected()) return; if (mExpectedSelStart != mExpectedSelEnd) return; // already something selected - final TextRange range = getWordRangeAtCursor(spacingAndPunctuations, script, false); + final TextRange range = getWordRangeAtCursor(spacingAndPunctuations, script); if (range == null) return; mIC.setSelection(mExpectedSelStart - range.getNumberOfCharsInWordBeforeCursor(), mExpectedSelStart + range.getNumberOfCharsInWordAfterCursor()); } @@ -766,7 +802,7 @@ private static boolean isPartOfCompositionForScript(final int codePoint, * @return a range containing the text surrounding the cursor */ @Nullable public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, - final String script, final boolean justDeleted) { + final String script) { mIC = mParent.getCurrentInputConnection(); if (!isConnected()) { return null; @@ -785,26 +821,6 @@ private static boolean isPartOfCompositionForScript(final int codePoint, return null; } - // we need text before, and text after is either empty or a separator or similar - if (justDeleted && before.length() > 0 && - (after.length() == 0 - || !isPartOfCompositionForScript(Character.codePointAt(after, 0), spacingAndPunctuations, script) - ) - ) { - // issue: - // type 2 words and space, press delete twice -> remaining word and space before are selected - // now on next key press, the space before the word is removed - // or complete a word by choosing a suggestion, then press backspace -> same thing - // what is sometimes happening (depending on app, or maybe input field attributes): - // we just pressed delete, and getTextBeforeCursor gets the correct text, - // but getTextBeforeCursorAndDetectLaggyConnection returns the old word, before the deletion (not sure why) - // -> we try to detect this difference, and then try to fix it - // interestingly, getTextBeforeCursor seems to only get the correct text because it uses - // mCommittedTextBeforeComposingText, where the text is cached - // what could be actually going on? we probably need to fetch the text, because we want updated styles (if any) - before = fixIncorrectLength(before); - } - // Going backward, find the first breaking point (separator) int startIndexInBefore = before.length(); int endIndexInAfter = -1; @@ -890,37 +906,6 @@ private static boolean isPartOfCompositionForScript(final int codePoint, hasUrlSpans); } - // mostly fixes an issue where the space before the word is selected after deleting a codepoint, - // because the text length is not yet updated in the field (i.e. trying to select "word length" - // before cursor, but the last letter has just been deleted and thus the space before is also selected) - private CharSequence fixIncorrectLength(final CharSequence before) { - // don't use codepoints, just do the simple thing... - int initialCheckLength = Math.min(3, before.length()); - // this should have been checked before calling this method, but better be safe - if (initialCheckLength == 0) return before; - final CharSequence lastCharsInBefore = before.subSequence(before.length() - initialCheckLength, before.length()); - final CharSequence lastCharsBeforeCursor = getTextBeforeCursor(initialCheckLength, 0); - // if the last 3 chars are equal, we can be relatively sure to not have this bug (can still be e.g. rrrr, which is not detected) - // (we could also check everything though, it's just a little slower) - if (TextUtils.equals(lastCharsInBefore, lastCharsBeforeCursor)) return before; - - // delete will hopefully have deleted a codepoint, not only a char - // we want to compare whether the text before the cursor is the same as "before" without - // the last codepoint. if yes, return "before" without the last codepoint - final int lastBeforeCodePoint = Character.codePointBefore(before, before.length()); - int lastBeforeLength = Character.charCount(lastBeforeCodePoint); - final CharSequence codePointBeforeCursor = getTextBeforeCursor(lastBeforeLength, 0); - if (codePointBeforeCursor == null || codePointBeforeCursor.length() == 0) return before; - - // now check whether they are the same if the last codepoint of before is removed - final CharSequence beforeWithoutLast = before.subSequence(0, before.length() - lastBeforeLength); - final CharSequence beforeCursor = getTextBeforeCursor(beforeWithoutLast.length(), 0); - if (beforeCursor == null || beforeCursor.length() != beforeWithoutLast.length()) return before; - if (TextUtils.equals(beforeCursor, beforeWithoutLast)) - return beforeWithoutLast; - return before; - } - public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations, boolean checkTextAfter) { if (checkTextAfter && isCursorFollowedByWordCharacter(spacingAndPunctuations)) { diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index e844e12dc..c813223f6 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -1353,9 +1353,7 @@ String getWordAtCursor(final SettingsValues settingsValues, final String current if (!mConnection.hasSelection() && settingsValues.isSuggestionsEnabledPerUserSettings() && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { - final TextRange range = mConnection.getWordRangeAtCursor( - settingsValues.mSpacingAndPunctuations, - currentKeyboardScript, false); + final TextRange range = mConnection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentKeyboardScript); if (range != null) { return range.mWord.toString(); } @@ -1709,8 +1707,7 @@ public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues setting mConnection.finishComposingText(); return; } - final TextRange range = - mConnection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentKeyboardScript, true); + final TextRange range = mConnection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentKeyboardScript); if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) { // Race condition, or touching a word in a non-supported script. From b7fa85dcee19a15a419162d0e35567487f0a17f7 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 11 Jun 2024 22:12:31 +0200 Subject: [PATCH 23/92] fix broken tests --- .../helium314/keyboard/latin/InputLogicTest.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt index b7189946c..3d438db87 100644 --- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt +++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt @@ -635,11 +635,7 @@ class InputLogicTest { checkConnectionConsistency() } - private fun getWordAtCursor() = connection.getWordRangeAtCursor( - settingsValues.mSpacingAndPunctuations, - currentScript, - false - )?.mWord + private fun getWordAtCursor() = connection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentScript)?.mWord private fun setCursorPosition(start: Int, end: Int = start, weirdTextField: Boolean = false) { val ei = EditorInfo() @@ -917,9 +913,16 @@ private val ic = object : InputConnection { } return true } + // implementation is only to work with getTextBeforeCursorAndDetectLaggyConnection + override fun getExtractedText(p0: ExtractedTextRequest?, p1: Int): ExtractedText { + return ExtractedText().also { + it.startOffset = 0 + it.selectionStart = selectionStart + it.selectionEnd = selectionEnd + } + } // implement only when necessary override fun getCursorCapsMode(p0: Int): Int = TODO("Not yet implemented") - override fun getExtractedText(p0: ExtractedTextRequest?, p1: Int): ExtractedText = TODO("Not yet implemented") override fun deleteSurroundingTextInCodePoints(p0: Int, p1: Int): Boolean = TODO("Not yet implemented") override fun commitCompletion(p0: CompletionInfo?): Boolean = TODO("Not yet implemented") override fun commitCorrection(p0: CorrectionInfo?): Boolean = TODO("Not yet implemented") From 0e992dd8b937622663bc1fb1a56dbcc58e1b766b Mon Sep 17 00:00:00 2001 From: Timo <67267822+TimoNotThy@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:13:44 +0000 Subject: [PATCH 24/92] fixed typos in layouts.md #870 (#871) --- layouts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layouts.md b/layouts.md index 28bbfd596..c73614710 100644 --- a/layouts.md +++ b/layouts.md @@ -32,8 +32,8 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm * `multi_text_key`: key with an array of code points, e.g. `{ "$": "multi_text_key", "codePoints": [2509, 2480], "label": "্র" }` * there are also selector classes, which allow to change keys conditionally, see the [dvorak layout](https://github.com/Helium314/HeliBoard/blob/main/app/src/main/assets/layouts/dvorak.json) for an example: * `case_selector`: keys for `lower` and `upper` (both mandatory), similar to `shift_state_selector` - * `shift_state_selector`: keys for `unshifted`, `shifted`, `shiftedManual`, `shiftedAutomatic`, `capsLock`, `manualOrLocked`, `default` (all opttional) - * `variation_selector`: keys for `datetime`, `time`, `date`, `password`, `normal`, `uri`, `email`, `default` (all opttional) + * `shift_state_selector`: keys for `unshifted`, `shifted`, `shiftedManual`, `shiftedAutomatic`, `capsLock`, `manualOrLocked`, `default` (all optional) + * `variation_selector`: keys for `datetime`, `time`, `date`, `password`, `normal`, `uri`, `email`, `default` (all optional) * `layout_direction_selector`: keys for `ltr` and `rtl` (both mandatory) ### Properties * A (non-selector) key can have the following properties: From 5b1f40f0f6c415a17e554df217a75df98598da3a Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 12 Jun 2024 20:24:16 +0200 Subject: [PATCH 25/92] do a sanity check when setting composing text on text fields that have suggestions disabled fixes #225 could cause unnecessary text / suggestion reloads or performance regressions, though not found in testing --- .../keyboard/latin/RichInputConnection.java | 17 ++++++++++++++++- .../keyboard/latin/inputlogic/InputLogic.java | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 2cd1e0b41..c1dc91edb 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -18,6 +18,7 @@ import android.text.style.CharacterStyle; import helium314.keyboard.keyboard.KeyboardSwitcher; +import helium314.keyboard.latin.settings.Settings; import helium314.keyboard.latin.utils.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -651,7 +652,9 @@ public void setComposingRegion(final int start, final int end) { } } - public void setComposingText(final CharSequence text, final int newCursorPosition) { + // return whether the text was (probably) set correctly + // unfortunately this is necessary in some cases + public boolean setComposingText(final CharSequence text, final int newCursorPosition) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mExpectedSelStart += text.length() - mComposingText.length(); @@ -662,8 +665,20 @@ public void setComposingText(final CharSequence text, final int newCursorPositio // newCursorPosition != 1. if (isConnected()) { mIC.setComposingText(text, newCursorPosition); + if (!Settings.getInstance().getCurrent().mInputAttributes.mShouldShowSuggestions && text.length() > 0) { + // We have a field that disables suggestions, but still committed text is set. + // This might lead to weird bugs (e.g. https://github.com/Helium314/HeliBoard/issues/225), so better do + // a sanity check whether the wanted text has been set. + // Note that the check may also fail because the text field is not yet updated, so we don't want to check everything! + final CharSequence lastChar = mIC.getTextBeforeCursor(1, 0); + if (lastChar == null || lastChar.length() == 0 || text.charAt(text.length() - 1) != lastChar.charAt(0)) { + Log.w(TAG, "did set " + text + ", but got " + mIC.getTextBeforeCursor(text.length(), 0) + " as last character"); + return false; + } + } } if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); + return true; } /** diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index c813223f6..88f4b9ca1 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -2451,7 +2451,9 @@ private void setComposingTextInternalWithBackgroundColor(final CharSequence newC spannable.setSpan(backgroundColorSpan, 0, spanLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); composingTextToBeSet = spannable; } - mConnection.setComposingText(composingTextToBeSet, newCursorPosition); + if (!mConnection.setComposingText(composingTextToBeSet, newCursorPosition)) + // inconsistency in set and found composing text, better cancel composing (should be restarted automatically) + mWordComposer.reset(); } /** From d91350524a15e71653c7d0495dd29e2c8cfe95a5 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 12 Jun 2024 22:46:42 +0200 Subject: [PATCH 26/92] move cursor in whole codepoints on space / delete swipe and when space-swiping across emoji, send fake arrow keypress instead (discouraged, but avoids e.g. splitting pirate flag emoji and thus justified) fixes #859 --- .../keyboard/KeyboardActionListenerImpl.kt | 56 +++++++++++++++---- .../keyboard/latin/common/StringUtils.java | 2 +- .../keyboard/latin/common/StringUtils.kt | 2 +- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index 36ad42ebe..1df14f525 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -6,6 +6,9 @@ import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.InputPointers +import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.common.loopOverCodePoints +import helium314.keyboard.latin.common.loopOverCodePointsBackwards import helium314.keyboard.latin.inputlogic.InputLogic import helium314.keyboard.latin.settings.Settings import kotlin.math.abs @@ -80,7 +83,23 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp override fun onMoveDeletePointer(steps: Int) { inputLogic.finishInput() val end = inputLogic.mConnection.expectedSelectionEnd - val start = inputLogic.mConnection.expectedSelectionStart + steps + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + if (steps > 0) { + val text = inputLogic.mConnection.getSelectedText(0) + if (text == null) actualSteps = steps + else loopOverCodePoints(text) { + actualSteps += Character.charCount(it) + actualSteps >= steps + } + } else { + val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0) + if (text == null) actualSteps = steps + else loopOverCodePointsBackwards(text) { + actualSteps -= Character.charCount(it) + actualSteps <= steps + } + } + val start = inputLogic.mConnection.expectedSelectionStart + actualSteps if (start > end) return inputLogic.mConnection.setSelection(start, end) } @@ -120,29 +139,44 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp private fun onMoveCursorHorizontally(rawSteps: Int): Boolean { if (rawSteps == 0) return false - var steps = rawSteps // for RTL languages we want to invert pointer movement - if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) steps = -steps + val steps = if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) -rawSteps else rawSteps val moveSteps: Int if (steps < 0) { - val availableCharacters = inputLogic.mConnection.getTextBeforeCursor(64, 0)?.length ?: return false - moveSteps = if (availableCharacters < -steps) -availableCharacters else steps + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0) ?: return false + loopOverCodePointsBackwards(text) { + if (StringUtils.mightBeEmoji(it)) { + actualSteps = 0 + return@loopOverCodePointsBackwards true + } + actualSteps -= Character.charCount(it) + actualSteps <= steps + } + moveSteps = -text.length.coerceAtMost(abs(actualSteps)) if (moveSteps == 0) { // some apps don't return any text via input connection, and the cursor can't be moved // we fall back to virtually pressing the left/right key one or more times instead - while (steps != 0) { + repeat(-steps) { onCodeInput(KeyCode.ARROW_LEFT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false) - ++steps } return true } } else { - val availableCharacters = inputLogic.mConnection.getTextAfterCursor(64, 0)?.length ?: return false - moveSteps = availableCharacters.coerceAtMost(steps) + var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint + val text = inputLogic.mConnection.getTextAfterCursor(steps * 4, 0) ?: return false + loopOverCodePoints(text) { + if (StringUtils.mightBeEmoji(it)) { + actualSteps = 0 + return@loopOverCodePoints true + } + actualSteps += Character.charCount(it) + actualSteps >= steps + } + moveSteps = text.length.coerceAtMost(actualSteps) if (moveSteps == 0) { - while (steps != 0) { + repeat(steps) { onCodeInput(KeyCode.ARROW_RIGHT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false) - --steps } return true } diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java index 36da83ac2..6aa5f83bc 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.java @@ -625,7 +625,7 @@ public static boolean hasLineBreakCharacter(@Nullable final String text) { return false; } - public static boolean mightBeEmoji(final String s) { + public static boolean mightBeEmoji(final CharSequence s) { int offset = 0; final int length = s.length(); while (offset < length) { diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt index a12220387..3b53668a1 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt @@ -109,7 +109,7 @@ fun String.decapitalize(locale: Locale): String { fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c)) -fun isEmoji(s: String): Boolean = mightBeEmoji(s) && s.matches(emoRegex) +fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex) fun String.splitOnWhitespace() = split(whitespaceSplitRegex) From 6a34f2c832e3b9d7c75dd8887f4f1c5ca0b1cf78 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 12 Jun 2024 23:54:23 +0200 Subject: [PATCH 27/92] improve emoji detection when deleting still has issues with hand + skin tone emojis --- .../helium314/keyboard/latin/RichInputConnection.java | 10 ++++++---- .../helium314/keyboard/latin/common/StringUtils.kt | 2 ++ .../keyboard/latin/inputlogic/InputLogic.java | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index c1dc91edb..87e699fe6 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -1072,10 +1072,12 @@ public boolean spaceBeforeCursor() { public int getCharCountToDeleteBeforeCursor() { final int lastCodePoint = getCodePointBeforeCursor(); - if (!Character.isSupplementaryCodePoint(lastCodePoint)) return 1; - if (!StringUtils.mightBeEmoji(lastCodePoint)) return 2; - final String text = mCommittedTextBeforeComposingText.toString() + mComposingText; - return StringUtilsKt.getFullEmojiAtEnd(text).length(); + if (StringUtils.mightBeEmoji(lastCodePoint)) { + final String text = mCommittedTextBeforeComposingText.toString() + mComposingText; + final int emojiLength = StringUtilsKt.getFullEmojiAtEnd(text).length(); + if (emojiLength > 0) return emojiLength; + } + return Character.isSupplementaryCodePoint(lastCodePoint) ? 2 : 1; } public boolean hasLetterBeforeLastSpaceBeforeCursor() { diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt index 3b53668a1..8f3347129 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt @@ -64,6 +64,8 @@ fun getFullEmojiAtEnd(s: CharSequence): String { // stop if codepoint can't be emoji if (!mightBeEmoji(codepoint)) return "" offset -= Character.charCount(codepoint) + // todo: if codepoint in 0x1F3FB..0x1F3FF -> combine with other emojis in front, but only if they actually combine + // why isn't this done with zwj like everything else? skin tones can be emojis by themselves... if (offset > 0 && text[offset - 1].code == KeyCode.ZWJ) { offset -= 1 continue diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 88f4b9ca1..2c7b2b248 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -1311,7 +1311,7 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu // TODO: Add a new StatsUtils method onBackspaceWhenNoText() return; } - final int lengthToDelete = Character.isSupplementaryCodePoint(codePointBeforeCursor) + final int lengthToDelete = codePointBeforeCursor > 0xFE00 ? mConnection.getCharCountToDeleteBeforeCursor() : 1; mConnection.deleteTextBeforeCursor(lengthToDelete); int totalDeletedLength = lengthToDelete; @@ -1324,7 +1324,7 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu final int codePointBeforeCursorToDeleteAgain = mConnection.getCodePointBeforeCursor(); if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { - final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(codePointBeforeCursorToDeleteAgain) + final int lengthToDeleteAgain = codePointBeforeCursor > 0xFE00 ? mConnection.getCharCountToDeleteBeforeCursor() : 1; mConnection.deleteTextBeforeCursor(lengthToDeleteAgain); totalDeletedLength += lengthToDeleteAgain; From 5ccb79a0786418ee36d47264c4bc380985befd9d Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 12 Jun 2024 23:59:33 +0200 Subject: [PATCH 28/92] disable preview popup for language switch and emoji keys --- app/src/main/java/helium314/keyboard/keyboard/Key.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 1877b02f8..087cca130 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -1156,9 +1156,10 @@ public KeyParams( actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; switch (mCode) { case KeyCode.DELETE, KeyCode.SHIFT, Constants.CODE_ENTER, KeyCode.SHIFT_ENTER, KeyCode.ALPHA, Constants.CODE_SPACE, KeyCode.NUMPAD, - KeyCode.SYMBOL, KeyCode.SYMBOL_ALPHA -> actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; // no preview even if icon! - case KeyCode.SETTINGS, KeyCode.LANGUAGE_SWITCH -> actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING; + KeyCode.SYMBOL, KeyCode.SYMBOL_ALPHA, KeyCode.LANGUAGE_SWITCH, KeyCode.EMOJI, KeyCode.CLIPBOARD -> actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; // no preview even if icon! } + if (mCode == KeyCode.SETTINGS || mCode == KeyCode.LANGUAGE_SWITCH) + actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING; if (mCode == KeyCode.DELETE) actionFlags |= ACTION_FLAGS_IS_REPEATABLE; mActionFlags = actionFlags; From 3add4938885e6004036612627854151b91d6b124 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 13 Jun 2024 00:21:41 +0200 Subject: [PATCH 29/92] version and translations --- app/build.gradle | 4 +- .../main/assets/dictionaries_in_dict_repo.csv | 1 + app/src/main/res/values-ar/strings.xml | 11 + app/src/main/res/values-bn/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 19 ++ app/src/main/res/values-eu/strings.xml | 10 + app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-gl/strings.xml | 6 + app/src/main/res/values-in/strings.xml | 11 +- app/src/main/res/values-it/strings.xml | 226 +++++++++++------- app/src/main/res/values-nl/strings.xml | 11 +- app/src/main/res/values-pl/strings.xml | 3 +- app/src/main/res/values-ru/strings.xml | 2 + .../metadata/android/ar/changelogs/1003.txt | 2 + .../metadata/android/ar/changelogs/2000.txt | 1 + .../metadata/android/ar/changelogs/2001.txt | 11 + .../metadata/android/ar/full_description.txt | 7 +- .../android/de-DE/changelogs/2000.txt | 10 + .../android/de-DE/changelogs/2001.txt | 11 + .../android/de-DE/full_description.txt | 8 +- .../android/en-US/changelogs/2002.txt | 4 + .../android/it-IT/changelogs/1001.txt | 20 ++ .../android/it-IT/changelogs/1003.txt | 9 + .../android/it-IT/changelogs/1004.txt | 7 + .../android/it-IT/changelogs/2000.txt | 10 + .../android/it-IT/changelogs/2001.txt | 11 + .../android/it-IT/full_description.txt | 29 +++ .../android/it-IT/short_description.txt | 1 + fastlane/metadata/android/it-IT/title.txt | 1 + .../android/nl-NL/changelogs/2001.txt | 11 + .../android/nl-NL/full_description.txt | 11 +- .../android/pl-PL/changelogs/2001.txt | 11 + .../android/pl-PL/full_description.txt | 7 +- fastlane/metadata/android/ru-RU/title.txt | 1 + 34 files changed, 380 insertions(+), 110 deletions(-) create mode 100644 fastlane/metadata/android/ar/changelogs/2001.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/2000.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/2001.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/2002.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/1001.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/1003.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/1004.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/2000.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/2001.txt create mode 100644 fastlane/metadata/android/it-IT/full_description.txt create mode 100644 fastlane/metadata/android/it-IT/short_description.txt create mode 100644 fastlane/metadata/android/it-IT/title.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/2001.txt create mode 100644 fastlane/metadata/android/pl-PL/changelogs/2001.txt create mode 100644 fastlane/metadata/android/ru-RU/title.txt diff --git a/app/build.gradle b/app/build.gradle index da228679a..928869a3a 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "helium314.keyboard" minSdkVersion 21 targetSdkVersion 34 - versionCode 2001 - versionName '2.0-beta1' + versionCode 2002 + versionName '2.0-beta2' ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } diff --git a/app/src/main/assets/dictionaries_in_dict_repo.csv b/app/src/main/assets/dictionaries_in_dict_repo.csv index fe9800762..e8865006c 100644 --- a/app/src/main/assets/dictionaries_in_dict_repo.csv +++ b/app/src/main/assets/dictionaries_in_dict_repo.csv @@ -65,6 +65,7 @@ main,ur, main,af,exp main,ar,exp main,bn_BD,exp +main,eu,exp main,bn,exp main,bg,exp main,cs,exp diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0e5aa875a..337b7ecc8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -391,4 +391,15 @@ مفاتيح وظيفية المفاتيح الوظيفية (المزيد من الرموز) المفاتيح الوظيفية (الرموز) + تثبيت مفتاح شريط الأدوات عند الضغط لفترة طويلة + سيؤدي هذا إلى تعطيل إجراءات الضغط لفترة طويلة الأخرى لمفاتيح شريط الأدوات غير المثبتة + حدد مفاتيح شريط الأدوات المثبتة + تم نسخ المحتوى + سلوك مفتاح تبديل اللغة + إظهار شريط الأدوات تلقائيًا + إظهار شريط الأدوات في حالة بدء الإدخال أو تحديد النص + إخفاء شريط الأدوات تلقائيًا + إخفاء شريط الأدوات عندما تصبح الاقتراحات متاحة + شريط الأدوات + رمز تعبيري \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 5d4dfd094..a74fe5a2a 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -340,4 +340,5 @@ পরিবর্তনশীল টুলবার দিক ডান থেকে বাম কিবোর্ড নির্বাচন করলে উল্টো দিকে যাও %s (শিক্ষার্থী) + ভাষা পরিবর্তন বোতাম \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8aa02cc81..3a8b31baf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -349,4 +349,23 @@ Immer mittleren Vorschlag verwenden Wähle die Tasten der Symbolleiste für die Zwischenablage %s (Extended) + Angeheftete Tasten der Symbolleiste auswählen + Dadurch werden andere Langdruck-Aktionen für Tasten der Symbolleiste, die nicht angeheftet sind, deaktiviert + Inhalt kopiert + Symbolleiste automatisch anzeigen + Symbolleiste anzeigen, wenn die Eingabe beginnt oder Text ausgewählt ist + Symbolleiste automatisch ausblenden + Ausblenden der Symbolleiste, wenn Vorschläge verfügbar sind + Verhalten der Sprachumschalttaste + Pin-Symbolleisten-Taste bei langem Drücken + Symbolleiste + Emoji + Mansi (%s) + Mansi + Mehr Farben anzeigen + Diese Einstellung legt alle intern verwendeten Farben offen. Die Liste der Farben kann sich jederzeit ändern. Es gibt keine Standardfarbe, und die Namen werden nicht übersetzt. + Anpassen von Funktionstasten-Layouts + Funktionstasten + Funktionstasten (Symbole) + Funktionstasten (Mehr Symbole) \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7fb135ad8..494806ad5 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -354,4 +354,14 @@ Funtzio-teklak (ikur gehiago) Funtzio-teklak (ikurrak) Pertsonalizatu funtzio-teklen diseinua + Hautatu ainguratu beharreko tresna-barrako teklak + Ainguratu tresna-barrako tekla luze sakatzean + Ezkutatu automatikoki tresna-barra + Erakutsi automatikoki tresna-barra + Erakutsi tresna-barra sarrera hastean edo testua hautatzean + Honek ainguratuta ez dauden tresna-barrako teklak sakatu luzeko beste ekintza batzuk desgaituko ditu + Tresna-barra + Kopiatu da edukia + Hizkuntza aldatzeko teklaren portaera + Ezkutatu tresna-barra iradokizunak erabilgarri daudenean \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a96a98600..f8c382cdb 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -396,4 +396,6 @@ Nouveau dictionnaire: La barre d\'outils est masquée lorsque les suggestions sont disponibles La barre d\'outils s\'affiche si la saisie débute ou si le texte est sélectionné Afficher automatiquement la barre d\'outils + Comportement de la touche de changement de langue + Emoji \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 5bb7addc6..15575549b 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -362,4 +362,10 @@ Esto desactivará outras accións de pulsación longa para teclas da barra que non están fixadas Barra de ferramentas Copiouse o contido + Mostrar ferramentas automáticamente + Mostra a barra de ferramentas ao comezar a escribir ou seleccionar texto + Agochar ferramentas + Agocha a barra de ferramentas ao estar dispoñibles as suxestións + Comportamento da tecla de cambio de idioma + Emoji \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 235eba42f..8e74ca1ce 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -289,7 +289,7 @@ \nKamus baru: \n%3$s "Tanpa kamus, Anda hanya akan mendapatkan saran untuk teks yang Anda masukkan sebelumnya.<br> -\n Anda dapat mengunduh kamus %1$s, atau memeriksa apakah kamus untuk \"%2$s\" dapat diunduh secara langsung dari %3$s." +\n Anda bisa mengunduh kamus %1$s, atau memeriksa apakah kamus untuk \"%2$s\" dapat diunduh secara langsung dari %3$s." Hitam Mengonfigurasi papan ketik Pada bahasa apa kamus \"%1$s\" untuk %2$s harus ditambahkan? @@ -335,4 +335,13 @@ Holo Putih Ungu lembayung Lisensi Publik Umum GPU v3.0 + Kaitag + Kaitag (%s) + Tutup riwayat clipboard + Emoji + Potong + %s (Probhat) + Mansi + Mansi (%s) + Tampilkan lebih banyak warna \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2ca4c50c5..8c6370a00 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -4,8 +4,8 @@ modified SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only --> - "Cerca in nomi contatti" - "La funzione di controllo ortografico usa voci dell\'elenco contatti" + Nomi dei contatti + La funzione di controllo ortografico usa le voci dell\'elenco contatti "Vibrazione tasti" "Suono tasti" "Popup tasti" @@ -23,7 +23,7 @@ Utilizza i nomi dei contatti per suggerimenti e correzioni "Suggerimenti personalizzati" "Doppio spazio per punto" - Tocca due volte la barra spaziatrice per inserire un punto seguito da uno spazio + Doppio tocco sulla barra spaziatrice per inserire un punto seguito da uno spazio "Maiuscole automatiche" "Iniziale maiuscola per la prima parola di ogni frase" "Dizionario personale" @@ -31,10 +31,10 @@ "Dizionario principale" Mostra suggerimenti di correzione "Visualizza le parole suggerite durante la digitazione" - "Blocca parole offensive" - "Non suggerire parole potenzialmente offensive" + Blocca parole sensibili + Non suggerire parole potenzialmente inadatte ai minori "Correzione automatica" - Barra spaziatrice/punteggiatura correggono automaticamente le parole con errori + Le correzioni sono applicate in automatico all\'inserimento di uno spazio o di un segno di interpunzione Disattivata Moderata Aggressiva @@ -42,8 +42,8 @@ "Suggerimenti parola seguente" "Usa la parola precedente per i suggerimenti" "Attiva digitazione gestuale" - "Inserisci una parola scorrendo tra le lettere" - "Mostra traccia del gesto" + Digita le parole percorrendo con un gesto le lettere che le compongono + Mostra la traccia del gesto "Anteprima mobile dinamica" "Visualizza la parola suggerita durante il gesto" "Gesto frase" @@ -74,10 +74,10 @@ "Lingua" "Layout" Durata vibrazione tasti - Volume suono tasti - Ritardo pressione prolungata dei tasti - Emoji con la tastiera fisica - Il tasto fisico ALT mostra il selettore delle emoji + Volume del suono dei tasti + Pressione prolungata + Tasto fisico per passare alle emoji + Il tasto ALT di una tastiera collegata apre il pannello emoji "Predefinito" Benvenuto in %s con la digitazione gestuale @@ -85,19 +85,19 @@ "Passaggio successivo" Configurazione di %s Attiva %s - Seleziona \"%s\" nelle impostazioni Lingua e immissione del tuo dispositivo per autorizzarne l\'esecuzione. + Seleziona \"%s\" nelle impostazioni \'Lingua e immissione\' del tuo dispositivo per autorizzare l\'app. %s è già attiva nelle impostazioni Lingua e immissione. Attiva nelle impostazioni Passa a %s - Quindi, attiva \"%s\" come metodo di immissione del testo. + Infine, attiva \"%s\" come metodo di immissione di testo. "Cambia metodo di immissione" - "Grazie, adesso sei pronto!" - Puoi usare %s per digitare in tutte le tue app preferite. + Tutto pronto! + Puoi usare %s per digitare in qualsiasi app. Fine - "Mostra icona app" - "Mostra l\'icona dell\'app in Avvio app" - "Dizionari aggiuntivi" - "Impostazioni per dizionari" + Mostra l\'icona dell\'app + Mostra l\'icona nella schermata di avvio delle app + Dizionari + Impostazioni dei dizionari "Dizionario disponibile" Impossibile connettersi al servizio dei dizionari "Nessun dizionario" @@ -121,111 +121,111 @@ Alfabeto (Colemak Mod-DH) Spazio automatico dopo la punteggiatura Altri tasti - Inserisce automaticamente lo spazio dopo la punteggiatura, alla digitazione una nuova parola + Alla digitazione di una nuova parola, inserisce automaticamente lo spazio dopo la punteggiatura Alfabeto (Workman) Cronologia appunti %s min Nessun limite - Attiva la cronologia degli appunti - Se disattivato, il tasto appunti incolla il contenuto degli appunti, se presente - Periodo conservazione cronologia + Attiva la cronologia appunti + Se disattivata, il tasto \'Appunti\' incolla direttamente l\'ultimo contenuto + Durata dei contenuti nella cronologia HeliBoard - Controllo ortografico HeliBoard - Impostazioni controllo ortografico Riga dei numeri Scorri per cancellare - Scorri sul tasto Cancella per selezionare e rimuovere porzioni di testo più ampie in una sola volta - Forza modalità incognito + Scorri indietro partendo da \'Backspace\' per selezionare e rimuovere porzioni di testo più ampie in una sola volta + Forza la modalità incognito Tasto emoji Mostra sempre la riga dei numeri Mostra indicatori sui tasti Mostra gli indicatori di pressione prolungata dei tasti Cambia metodo di immissione con barra spaziatrice - Premendo a lungo la barra spaziatrice verrà visualizzato il menu di selezione del metodo di immissione + Premendo a lungo la barra spaziatrice si attiva il menu di selezione del metodo di immissione %s (Akkhor) - L\'aspetto seguirà le impostazioni di sistema + Il tema seguirà le impostazioni di sistema Tasti aggiuntivi Correzioni Suggerimenti - Sperimentali + Opzioni sperimentali Varie Immissione HeliBoard - Impostazioni Disattiva l\'apprendimento di nuove parole Bordi dei tasti - Modalità giorno/notte automatica + Tema chiaro/scuro automatico Modalità correzione automatica "Utilizza le lingue di sistema" "Scegli il metodo di immissione" "Annulla" - "Ripeti" - "Usa comunicazioni e dati digitati per migliorare i suggerimenti" + Ripristina + Usa i dati utente e le parole digitate per migliorare i suggerimenti "Vai" - "Succ." - "Prec." + + "Fine" "Invia" "Cerca" "Pausa" "Attendi" Mostra sempre i suggerimenti - In alto + Cursore su Input vocale Mostra indicatori di funzionalità - Selezionare un file in un formato compatibile. Le informazioni sui formati sono disponibili %s. + Seleziona un file in un formato compatibile. Più informazioni sui formati: %s. Errore nel ripristinare il backup: %s - Utilizza il dizionario personale del dispositivo per memorizzare le parole apprese + Le nuove parole saranno salvate nel dizionario utente locale del dispositivo Backup Lingua (priorità) Aggiungi parole al dizionario personale - Toccare per modificare il layout grezzo + Tocca per modificare il layout Lingue e layout - Impossibile leggere file + Impossibile leggere il file È necessaria la libreria per \'%s\'. Le librerie incompatibili potrebbero bloccarsi quando si utilizza la digitazione gestuale. \n \nAttenzione: il caricamento di codice esterno può essere un rischio per la sicurezza. Utilizzare solo librerie di provenienza affidabile. Elimina libreria Lingua - Destra - Preferire i numeri localizzati a quelli latini + Cursore a destra + Mostra i numeri localizzati anziché quelli latini File di libreria sconosciuto. Si è sicuri di averlo ottenuto da una fonte affidabile e che sia per \'%s\'? - Mostra le varianti definite nelle lingue della tastiera (impostazione predefinita) + Mostra le varianti definite dalle lingue della tastiera (impostazione predefinita) Aggiungi varianti comuni Appunti Riga dei numeri Ripristino Seleziona l\'ordine dei tasti a comparsa Seleziona la fonte degli indicatori - Fornisci una libreria nativa per abilitare la digitazione gestuale + Carica il file della libreria nativa per la digitazione gestuale Cerca di rilevare URL e simili come una singola parola - Carica libreria per la digitazione gestuale - In basso + Carica la libreria per la digitazione gestuale + Cursore giù Seleziona i tasti della barra degli strumenti Errore backup: %s - Mostra degli indicatori se premendo a lungo un tasto si attivano funzionalità aggiuntive + Indica se premendo a lungo un tasto si attivano funzionalità aggiuntive Backup e ripristino - A sinistra + Cursore tutto a sinistra Seleziona parola - Sinistra - Veramente eliminare layout personalizzato %s? - Aggiungi layout personalizzato + Cursore a sinistra + Vuoi davvero eliminare il layout personalizzato %s? + Aggiungi un layout personalizzato Simboli Rilevamento URL - A destra + Cursore tutto a destra Localizza la riga dei numeri Aggiungi tutte le varianti disponibili - Più autocorrezione - Distanza divisione - Ignora la richiesta di altre applicazioni di disabilitare i suggerimenti (può causare problemi) - Imposta nome layout + Correzione automatica aggiuntiva + Distanza tra le due parti + Ignora la richiesta di altre applicazioni di disabilitare i suggerimenti (potrebbe causare problemi) + Nome del layout Cambia lingua Pulisci appunti Riduci lo spazio tra i tasti - Attenzione: La disattivazione di questa impostazione cancella i dati appresi + Attenzione: la disattivazione di questa impostazione cancellerà tutti i dati memorizzati Carica libreria Carica file - Copia layout esistente - Mostra più lettere con diacritici nel popup - Modalità ad una mano + Duplica un layout esistente + Mostra più lettere accentate + Modalità a una mano Errore layout: %s Digitazione multilingue Salva o carica da file. Attenzione: il ripristino sovrascrive i dati esistenti @@ -234,48 +234,48 @@ Cambia entrambi %s (tipo tastiera Sebeolsik 390) %s (Tipo tastiera Sebeolsik Final) - Diurna + Chiaro Imposta un\'immagine di sfondo - Alphabet (tipo tastiera Bépo) + Alfabeto (Bépo) Altri simboli - Imposta immagine per modalità diurna o notturna? + Vuoi impostare l\'immagine per il tema chiaro o scuro? Dizionari Configura la tastiera - Notturna + Scuro Spaziatura dal bordo in basso - Regola i colori (notte) + Colori (Tema scuro) Scuro Mostra solo i colori principali Colori dinamici Aspetto Sfondo tasti - Colori - Sfondo tasto funzione - Questa parola è già presente nel dizionario utente %s. Si prega di digitarne un\'altra. + Colori (tema chiaro) + Sfondo dei tasti funzione + La parola è già presente nel dizionario utente %s. Arrotondato Nero Cioccolato Rimuovere davvero il dizionario \"%s\" aggiunto dall\'utente? - Nuvoloso + Grigio nuvola GNU General Public License v3.0 Errore: script non compatibile con questa tastiera Descrizione delle funzionalità nascoste Rosa qui Errore: Il file selezionato non è un file dizionario valido - dispositivo di archiviazione protetto + spazio di archiviazione protetto Colora la barra di navigazione Utilizza ancora Foresta Selezionare un dizionario da aggiungere. I dizionari in formato .dict possono essere scaricati %s. Errore nel caricamento del file del dizionario - Blu grigio - Testo barra spaziatrice + Grigio-blu + Testo della barra spaziatrice Chiaro Aggiungi una parola - Testo riga suggerimenti - Clicca per l\'anteprima - Definito dall\'utente + Testo dei suggerimenti + Tocca per vedere l\'anteprima + Personalizzato (Tema chiaro) Sostituisci dizionario Aggiungi a %s Sfondo della tastiera @@ -284,22 +284,22 @@ Versione Seleziona la lingua Caratteri dei tasti - Sfondo barra spaziatrice + Sfondo della barra spaziatrice Più scuro - Scegli colore automaticamente + Scegli automaticamente Informazioni Licenza open-source Oceano Sabbia Stile Mostra le funzionalità che potrebbero passare inosservate - Colori (notte) + Colori (tema scuro) Mostra tutti i colori Seleziona i colori per il testo e gli sfondi Caratteri degli indicatori dei tasti - Definito dall\'utente (notte) + Personalizzato (Tema scuro) Marrone - Sostituire davvero il dizionario aggiunto dall\'utente \"%1$s\"? + Vuoi davvero sostituire il dizionario aggiunto dall\'utente \"%1$s\"? \n \nDizionario attuale: \n%2$s @@ -314,13 +314,13 @@ Non mostrare più Il file selezionato è per %1$s, ma %2$s era previsto. Lo si usa ancora per %2$s? Chiudi - Input gestuale + Traccia dell\'input gestuale Tocca la lingua per aprire le impostazioni - Regola i colori + Colori (Tema chiaro) Salva log Holo bianco Dizionario interno principale - Aggiungi dizionario da file + Aggiungi un dizionario da file %s (sperimentale) Simboli Simboli (arabo) @@ -332,16 +332,68 @@ Simboli telefono %s (studenti) Kaitag - Gesto di scorrimento verticale sulla barra spaziatrice + Scorrimento verticale sulla barra spaziatrice Nessuno - Gesto di scorrimento orizzontale sulla barra spaziatrice + Scorrimento orizzontale sulla barra spaziatrice Sposta il cursore Kaitag (%s) %s (Probhat) Premi a lungo il tasto dei simboli per il tastierino numerico Taglia - Inverte la direzione quando è selezionato un sottotipo di tastiera da destra a sinistra - Direzione barra strumenti variabile + Inverte la direzione quando la tastiera è di tipo destra-sinistra + Direzione della barra degli strumenti Colore accento - "► Premere a lungo il tasto \"Appunti\" (quello opzionale nella barra dei suggerimenti) per incollare il contenuto degli appunti del sistema. <br> <br> ► Premere a lungo i tasti della barra degli strumenti per fissarli. <br> <br> ► Premere a lungo il tasto \",\" per accedere a \"Vista appunti\", \"Vista emoji\", \"Modalità a una mano\", \"Impostazioni\" o \"Cambio lingua\": <br> \t• \"Vista emoji\" e \"Cambio lingua\" scompaiono se il tasto corrispondente è abilitato; <br> \t• Per alcuni layout non è il tasto \",\", ma il tasto nella stessa posizione (es. \"q\" per il layout Dvorak). <br> <br> ► Quando la modalità in incognito è attiva, non verranno apprese parole e non verranno aggiunte emoji ai preferiti. <br> <br> ► Premere l\'icona \"Incognito\" per accedere alla barra degli strumenti. <br> <br> ► Scorrimento dei tasti: scorrere il dito dal tasto \"Maiusc\" a un altro tasto per digitare un singolo tasto maiuscolo: <br> \t• Questo funziona anche con il tasto \"?123\" per digitare un singolo simbolo dalla tastiera dei simboli e per i tasti correlati. <br> <br> ► Premere a lungo un suggerimento nella barra dei suggerimenti per visualizzarne altri e un pulsante di cancellazione per rimuoverlo. <br> <br> ► Scorrere il dito verso l\'alto da un suggerimento per aprire altri suggerimenti e rilasciarlo per selezionarlo. <br> <br> ► Premere a lungo una voce nella cronologia degli appunti per fissarla (rimarrà finché non verrà rimossa). <br> <br> ► È possibile aggiungere dizionari aprendoli da un gestore file: <br> \t• Questo funziona solo con <i>content-uris</i> e non con <i>file-uris</i>, il che significa che potrebbe non funzionare con alcuni gestori file. <br> <br> <i>Modalità debug / Debug APK</i> <br> <br> \t• Premere a lungo un suggerimento per mostrare il dizionario sorgente.<br> <br> \t• Quando si utilizza il debug APK, è possibile trovare \"Impostazioni di debug\" in \"Preferenze avanzate\". L\'utilità è limitata, tranne che per il dump dei dizionari nel registro. <br> <br> \t• In caso di arresto anomalo dell\'applicazione, all\'apertura delle Impostazioni verrà richiesto se si desidera il registro degli arresti anomali. <br> <br> \t• Quando si utilizza la digitazione multilingua, la barra spaziatrice mostrerà il valore di confidenza utilizzato per determinare la lingua utilizzata. <br> <br> \t• I suggerimenti avranno dei piccoli numeri in alto che mostrano un punteggio interno e il dizionario di origine (può essere disattivato). <br> <br> ► Per gli utenti che eseguono backup manuali con accesso root: a partire da Android 7, il file delle preferenze condivise non si trova nella posizione predefinita, perché l\'app utilizza %s. <br> Questo è necessario affinché le impostazioni possano essere lette prima che il dispositivo venga sbloccato, per esempio all\'avvio. <br> Il file si trova in \"/data/user_de/0/package_id/shared_prefs/\", ma può variare a seconda del dispositivo e della versione di Android. <br>" + ► La pressione prolungata dei tasti fissati sulla barra degli strumenti attivano funzionalità aggiuntive: +\n\t• Appunti &#65515; Incolla<br> +\n\t• Sposta a sinistra/destra &#65515; Sposta all\'inizio o alla fine della riga<br> +\n\t• Sposta su/giù &#65515; Pagina su/giù<br> +\n\t• Copia &#65515; Copia tutto<br> +\n\t• Seleziona parola &#65515; Seleziona tutto<br> +\n\t• Annulla &#8596; Ripristina<br><br> +\n► La pressione prolungata dei tasti nella barra dei suggerimenti li blocca sulla barra. +\n► Con la pressione prolungata del tasto virgola si accede alle funzionalità appunti, emoji, modalità una mano, impostazioni, cambio lingua. +\n► Quando è abilitata la modalità incognito, l\'apprendimento delle parole è sospeso e le emoji recenti non sono memorizzate. +\n► In modalità incognito, un tap sull\'icona \'Incognito\' permette di accedere alla barra degli strumenti. +\n► Scorrimento da \'Maiuscolo\': lo scorrimento dal tasto \'Maiuscolo\' a una lettera permette di digitare in maiuscolo solo il carattere scelto. +\n► È possibile tener premuto il tasto \'Maiuscolo\' o \'Simboli\', digitare uno o più caratteri e rilasciare per tornare alla tastiera precedente. +\n► La pressione lunga su un suggerimento mostra altri suggerimenti, più un tasto \'Elimina\' per rimuovere il suggerimento stesso. +\n► È possibile scorrere verso l\'alto da un suggerimento per aprire altri suggerimenti e rilasciare su un suggerimento per selezionarlo. +\n► È possibile toccare a lungo una voce della cronologia degli appunti per fissarla (mantenerla negli appunti finché non viene sbloccata nello stesso modo). +\n► È possibile eliminare facilmente una voce della cronologia degli appunti trascinandola verso sinistra fuori dallo schermo (tranne quando è bloccata). +\n► Dopo aver selezionato un testo, è possibile alternare tra varianti tutto in maiuscolo, tutto in minuscolo e maiuscola iniziale. +\n► È possibile aggiungere dizionari aprendoli da un qualsiasi gestore file. +\n\t• Supporta solo <i>content-uris</i>, non <i>file-uris</i>: potrebbe non funzionare con alcuni gestori di file.<br><br> +\n► Gli utenti che eseguono backup manuali con accesso root, non troveranno il file esportato nella cartella predefinita, perché l\'app usa %s al fine di permettere il caricamento delle impostazioni prima dello sblocco del dispositivo. +\n\t• Il file delle preferenze condivise è salvato nella cartella: <i>/data/user_de/0/package_id/shared_prefs/</i> (potrebbe variare in base al dispositivo e alla versione di Android). +\n<i><b>App di debug / Modalità debug</b></i><br><br> +\n► È possibile premere a lungo un suggerimento per mostrare il dizionario di origine. +\n► Quando si utilizza l\'APK di debug, è possibile trovare le impostazioni di debug tra le preferenze avanzate. Lo strumento permette lo scaricamento dei dizionari nel registro. +\n\t• Se si usa una versione \'normale\' dell\'app, si può accedere alla Modalità Sviluppatore toccando più volte la versione del sistema operativo in Informazioni (può variare in base al produttore): nelle impostazioni avanzate del dispositivo apparirà una nuova sezione \'Strumenti Sviluppatore\' che contiene gli strumenti di debug. +\n\t• Quando si abilita \'Mostra informazioni sui suggerimenti\', accanto ai suggerimenti appariranno alcuni indicatori numerici che corrispondono a determinati punteggi interni, più il dizionario di origine. +\n► In caso di arresto anomalo dell\'app, verrà chiesto se si desidera ottenere i registri al successivo accesso alle impostazioni. +\n► Se è attiva la digitazione multilingue, la barra spaziatrice mostrerà un codice che corrisponde alla lingua corrente. +\n► Per nascondere gli indicatori numerici accanto ai suggerimenti, disabilitare \'Mostra informazioni sui suggerimenti\' dalle impostazioni. + Scegli i tasti permanenti (icone visibili anche con la barra degli strumenti nascosta) + Fissa i tasti alla barra degli strumenti con una pressione lunga + Tasti funzione + La barra degli strumenti appare all\'inizio della digitazione o quando si seleziona un testo + Nascondi automaticamente la barra degli strumenti + Nasconde la barra degli strumenti quando è possibile mostrare le parole suggerite + Mostra automaticamente la barra degli strumenti + Le altre azioni di pressione prolungata per i tasti non fissati non saranno disponibili + %s (Extended) + Mansi (%s) + Tasti funzione (Simboli) + Tasti funzione (Altri simboli) + Barra degli strumenti + Usa sempre la parola suggerita al centro + Chiudi la cronologia appunti + Seleziona i tasti mostrati insieme alla cronologia appunti + Comportamento del tasto di cambio lingua + All\'aggiunta di uno spazio o di un segno di punteggiatura, il suggerimento centrale è inserito automaticamente + Personalizza i layout dei tasti funzione + Testo copiato + Mansi + Mostra altri colori + Questa impostazione mostra tutti i colori utilizzati internamente. L\'elenco dei colori può cambiare in qualsiasi momento. Non esiste un colore predefinito e i nomi non sono tradotti. \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a6f32b839..3cc51605b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -131,27 +131,27 @@ Klembordgeschiedenis inschakelen Meer toetsen Nummerrij altijd weergeven - Voer een veegbeweging uit vanaf de delete-toets om grotere delen van tekst tegelijk te selecteren en te verwijderen + Veeg vanaf de delete-toets om grotere stukken tekst in één keer te selecteren en verwijderen HeliBoard spellingcontrole HeliBoard spellingscontrole instellingen Nummerrij - Incognitomodus forceren + Incognito-modus afdwingen Emoji-toets Correcties Suggesties %smin Druk lang op de spatietoets voor het selectiemenu van de invoermethode - Autocorrectie vertrouwen + Betrouwbaarheid autocorrectie Indien uitgeschakeld, plakt de klembord-toets de inhoud van het klembord Hints bij toetsen weergeven Het leren van nieuwe woorden uitschakelen - Hints voor lang indrukken weergeven + Hints bij lang indrukken weergeven Toetsenbordhoogte schaal %s (Akkhor) Invoermethode schakelen met spatietoets Alfabet (Colemak Mod-DH) Alfabet (Workman) - Toetsen met randen + Toetsranden Automatisch spatie invoegen na interpunctie en het typen van een nieuw woord HeliBoard instellingen Invoer @@ -400,4 +400,5 @@ Verberg de werkbalk wanneer er suggesties beschikbaar komen Toon de werkbalk bij invoer of selectie van tekst Gedrag taalkeuze-toets + Emoji \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 293f527b7..5222ad441 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -357,7 +357,7 @@ Białe Holo Zmień oba %s (eksperymentalny) - Dostosuj układ symboli i liczb + Dostosuj układy symboli i liczb Symbole Symbole (arabski) Telefon @@ -400,4 +400,5 @@ Automatyczne ukrywanie paska narzędzi Automatyczne wyświetlanie paska narzędzi Zachowanie klawisza zmiany języka + Emotikony \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4bb26078e..fe776ca93 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -346,4 +346,6 @@ Длительное нажатие клавиши символов для цифровой клавиатуры %s (Студенческая) Вырезать + Поведение клавиши смены языка + Эмодзи \ No newline at end of file diff --git a/fastlane/metadata/android/ar/changelogs/1003.txt b/fastlane/metadata/android/ar/changelogs/1003.txt index 003dbf1db..665791b88 100644 --- a/fastlane/metadata/android/ar/changelogs/1003.txt +++ b/fastlane/metadata/android/ar/changelogs/1003.txt @@ -5,3 +5,5 @@ * إظهار أيقونات شريط الأدوات في مربع حوار مفتاح شريط الأدوات * إضافة زر إغلاق في سجل الحافظة بواسطة @codokie (#403, #649) * إضافة التصميم الروسي (الطالب) بواسطة @Zolax9 (#640) +* جعل لوحة الأرقام على مفتاح الرموز اختيارية بالضغط لفترة طويلة (#588) +* إصلاحات وتحسينات طفيفة، بما في ذلك #632، #637، #638 بواسطة @RHJihan diff --git a/fastlane/metadata/android/ar/changelogs/2000.txt b/fastlane/metadata/android/ar/changelogs/2000.txt index 02ca6ed59..f16111e78 100644 --- a/fastlane/metadata/android/ar/changelogs/2000.txt +++ b/fastlane/metadata/android/ar/changelogs/2000.txt @@ -5,5 +5,6 @@ * السماح بتخصيص كافة الألوان * إضافة إعداد لإظهار الكلمة التي سيتم إدخالها دائمًا كاقتراح وسط * إضافة مؤشر قفل الحروف الكبيرة +* إضافة تخطيطات بييمونتي، وماري الشرقية، ومانسي، وتخطيطات موسعة للغة الكانادا والمجرية * إصلاح النص المقطوع في نافذة المعاينة الرئيسية المنبثقة على بعض الأجهزة * مزيد من الإصلاحات والتحسينات، راجع ملاحظات الإصدار diff --git a/fastlane/metadata/android/ar/changelogs/2001.txt b/fastlane/metadata/android/ar/changelogs/2001.txt new file mode 100644 index 000000000..a1b89da5e --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/2001.txt @@ -0,0 +1,11 @@ +* السماح بتخصيص تخطيطات المفاتيح الوظيفية +* ضبط الرموز قليلاً والمزيد من تخطيطات الرموز +* إضافة خيارات للإظهار/الإخفاء التلقائي لشريط الأدوات +* إضافة إشعار نخب عند نسخ النص +* سلوك مفتاح تبديل اللغة المنفصل عن التمكين +* إضافة النوافذ المنبثقة لمفتاح الفاصلة لتخطيطات الأرقام والهواتف +* جعل التثبيت بالضغط لفترة طويلة في شريط الأدوات اختياريًا +* نقل إعدادات شريط الأدوات إلى قسم منفصل +* إضافة مفتاح علامة التبويب +* فهم ctrl وشريط الأدوات والتسميات الرئيسية الأخرى في التخطيطات +* إصلاحات وتحسينات طفيفة diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt index 3bf6a3aa4..5ce7ca16b 100644 --- a/fastlane/metadata/android/ar/full_description.txt +++ b/fastlane/metadata/android/ar/full_description.txt @@ -5,7 +5,7 @@ HeliBoard عبارة عن لوحة مفاتيح مفتوحة المصدر تهت
    • أضف قواميس للاقتراحات والتدقيق الإملائي
      • -
      • قم ببناء قاموس خاص بك، أو احصل عليه هنا، أو في القسم التجريبي (قد تختلف الجودة)
      • +
      • قم ببناء قاموس خاص بك، أو احصل عليه هنا، أو في القسم التجريبي (قد تختلف الجودة)
      • يمكن استخدام قواميس إضافية للرموز التعبيرية أو الرموز العلمية لتقديم اقتراحات (على غرار "البحث عن الرموز التعبيرية")
      • لاحظ أنه بالنسبة للتخطيطات الكورية، تعمل الاقتراحات فقط باستخدام هذا القاموس، والأدوات الموجودة في مستودع القاموس غير قادرين على إنشاء قواميس صالحة للعمل
      @@ -15,15 +15,16 @@ HeliBoard عبارة عن لوحة مفاتيح مفتوحة المصدر تهت
    • يمكنه متابعة الألوان الديناميكية لنظام التشغيل Android 12+
  • تخصيص لوحة المفاتيح تخطيطات (متوفرة فقط عند تعطيل استخدام لغات النظام)
  • +
  • تخصيص تخطيطات خاصة، مثل الرموز أو الأرقام أو تخطيط المفتاح الوظيفي
  • الكتابة بلغات متعددة
  • الكتابة بالتمرير (فقط مع مكتبة المصادر المغلقة ☹️)
    • المكتبة غير مضمنة في التطبيق، حيث لا تتوفر مكتبة متوافقة مفتوحة المصدر
    • -
    • يمكن استخراجه من حزم GApps ("swypelibs")، أو تنزيله هنا
    • +
    • يمكن استخراجه من حزم GApps ("swypelibs")، أو تنزيله هنا (انقر على الملف ثم "خام" أو زِر التنزيل الصغير )
  • سجل الحافظة
  • وضع اليد الواحدة
  • تقسيم لوحة المفاتيح (متاح فقط إذا كانت الشاشة كبيرة بدرجة كافية)
  • لوحة الأرقام
  • -
  • النسخ الاحتياطي واستعادة بيانات الكلمات / السجل التي تعلمتها
  • +
  • النسخ الاحتياطي واستعادة الإعدادات الخاصة بك وبيانات الكلمات / التاريخ المستفادة
diff --git a/fastlane/metadata/android/de-DE/changelogs/2000.txt b/fastlane/metadata/android/de-DE/changelogs/2000.txt new file mode 100644 index 000000000..5869b2973 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/2000.txt @@ -0,0 +1,10 @@ +* Grundlegende Unterstützung für Modifikatortasten hinzugefügt +* Langdruck-Funktionen zu weiteren Tasten der Symbolleiste hinzugefügt +* und mehr Tasten in der Symbolleiste für den Verlauf der Zwischenablage +* Symbolleiste für den Verlauf der Zwischenablage anpassbar machen +* Erlaubt das Anpassen aller Farben +* Einstellung hinzugefügt, um das einzugebende Wort immer als mittleren Vorschlag anzuzeigen +* Anzeige für Feststelltaste hinzugefügt +* Piedmontese, Eastern Mari, Mansi, erweiterte Layouts für Kannada und Ungarisch hinzugefügt +* Abgeschnittener Text im Tastenvorschau-Popup auf einigen Geräten behoben +* weitere Korrekturen und Verbesserungen, siehe Versionshinweise diff --git a/fastlane/metadata/android/de-DE/changelogs/2001.txt b/fastlane/metadata/android/de-DE/changelogs/2001.txt new file mode 100644 index 000000000..e5cb206a3 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/2001.txt @@ -0,0 +1,11 @@ +* Anpassung des Layouts der Funktionstasten ermöglichen +* leichte Anpassung der Symbole und weitere Symbol-Layouts +* Optionen zum automatischen Ein-/Ausblenden der Symbolleiste hinzugefügt +* Toast-Benachrichtigung beim Kopieren von Text hinzugefügt +* Verhalten der Sprachumschalttaste von der Aktivierung trennen +* Hinzufügen von Komma-Popups für Nummern- und Telefon-Layouts +* Anheften in der Symbolleiste durch langes Drücken optional machen +* Symbolleisteneinstellungen in einen separaten Abschnitt verschieben +* Tabulator-Taste hinzugefügt +* Strg, Symbolleiste und andere Tastenbeschriftungen in Layouts verstehen +* kleinere Korrekturen und Verbesserungen diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt index de046a148..f2998420f 100644 --- a/fastlane/metadata/android/de-DE/full_description.txt +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -14,16 +14,18 @@ Features:
  • kann die Tag-/Nachteinstellung des Systems auf Android 10+ (und auf einigen Versionen von Android 9) verfolgen
  • kann dynamischen Farben für Android 12+ folgen
  • -
  • Tastatur-Layouts anpassen (nur verfügbar, wenn Systemsprachen verwenden)
  • +
  • Tastatur-Layouts anpassen (nur verfügbar, wenn Systemsprachen verwenden) +
  • Anpassen spezieller Layouts, wie Symbole, Zahlen oder Funktionstastenlayout
  • Mehrsprachiges Tippen
  • Glide Typing (nur mit quellgeschlossener Bibliothek ☹️)
    • Bibliothek nicht in der App enthalten, da keine kompatible quelloffene Bibliothek verfügbar ist
    • -
    • kann aus GApps-Paketen („swypelibs“) extrahiert oder heruntergeladen werden hier
    • +
    • kann aus GApps-Paketen („swypelibs“) extrahiert oder heruntergeladen werden hier +(klicken auf die Datei und dann auf "raw" oder den kleinen Download-Button)
  • Zwischenablageverlauf
  • Einhandmodus
  • Geteilte Tastatur (nur verfügbar, wenn der Bildschirm groß genug ist)
  • Ziffernblock
  • -
  • Sichere gelernte Wort-/Verlaufsdaten und stelle sie wieder her
  • +
  • Sichere und stelle deine Einstellungen und gelernte Wort-/Verlaufsdaten wieder her
  • diff --git a/fastlane/metadata/android/en-US/changelogs/2002.txt b/fastlane/metadata/android/en-US/changelogs/2002.txt new file mode 100644 index 000000000..751dfe142 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/2002.txt @@ -0,0 +1,4 @@ +* add emoji toolbar key, by @codokie (#845, #837) +* improvements regarding duplicated letters (#225 and maybe others) +* avoid positioning cursor inside emojis (#859) +* minor fixes for recently added features diff --git a/fastlane/metadata/android/it-IT/changelogs/1001.txt b/fastlane/metadata/android/it-IT/changelogs/1001.txt new file mode 100644 index 000000000..5d4f08073 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/1001.txt @@ -0,0 +1,20 @@ +* nuova icona di @FabianOvrWrt con contributi da @the-eclectic-dyslexic (#517, #592) +* trackpad della barra spaziatrice e cambio lingua maggiormente personalizzabile, di @arcarum (#486) +* aggiungi % al layout dei simboli shift (#568, #428) +* migliorato il comportamento del tasto di cambio lingua se è impostato per cambiare sia la lingua che la tastiera +* mostra i link ai dizionari esistenti quando si aggiunge un dizionario +* aggiungi il layout Kaitag, di @alkaitagi (#519) +* aggiungi il layout Probhat, di @fahimscirex (#489) +* icone in ordine inverso sulla barra degli strumenti per le lingue destra-sinistra, di @codokie (#557, #574) +* personalizzazione di layout speciali (tastierino numerico, telefono...) +* (ancora sperimentale, poiché i layout di base potrebbero cambiare) +* spellchecker.xml aggiornato per includere le impostazioni locali in cui sono disponibili i dizionari non inclusi nell'app +* traduzioni aggiornate (grazie a tutti i traduttori!) +* aggiornamento ndk, di @Syphyr (#560) +* riempimento automatico in linea aggiornato, di @arcarum (#595) +* risolto un problema con la finestra di dialogo del tasto della barra degli strumenti (#505) +* risolto un problema con il layout turco (#508) +* corretti gli stati di switch errati quando si ruota nella schermata Personalizza colori (#563) +* risolto il problema delle emoji recenti che non si caricano (#527) +* risolto il problema con i numeri che non sono visualizzati in determinati campi (#585) +* alcune correzioni minori diff --git a/fastlane/metadata/android/it-IT/changelogs/1003.txt b/fastlane/metadata/android/it-IT/changelogs/1003.txt new file mode 100644 index 000000000..a21f3b322 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/1003.txt @@ -0,0 +1,9 @@ +* aggiornamento delle icone per i tasti di correzione automatica e selezione nella barra degli strumenti, di @codokie (#524, #651) +* aggiunto il layout Chuvash, di @tenextractor (#677) +* aggiunto il tasto 'Taglia' alla barra degli strumenti, di @codokie (#678) +* layout Probhat aggiornato, di @fahimscirex (#628) +* le impostazioni relative alla della barra degli strumenti mostrano le icone degli strumenti +* aggiunto il pulsante 'Chiudi' nella cronologia degli appunti, di @codokie (#403, #649) +* aggiunto il layout Russo (Studente), di @Zolax9 (#640) +* impostazione che rende opzionale la comparsa del tastierino numerico alla pressione prolungata del tasto dei simboli (#588) +* correzioni e miglioramenti minori, inclusi #632, #637, #638, di @RHJihan diff --git a/fastlane/metadata/android/it-IT/changelogs/1004.txt b/fastlane/metadata/android/it-IT/changelogs/1004.txt new file mode 100644 index 000000000..8c4b6fb7d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/1004.txt @@ -0,0 +1,7 @@ +- Layout cirillico serbo aggiornato, di @markokocic (#704, #705) +- Layout estone aggiornato, di @tenextractor (#693) +- correzione di voci duplicate nella cronologia degli appunti, di @codokie (#616, #680) +- aggiunta solo di voci di testo alla cronologia degli appunti, di @codokie (#711) +- immagini migliori nei metadati, di @RHJihan (#713) +- impostazione corretta del colore delle icone sulla barra degli strumenti, di @codokie (#715, #716) +- altre correzioni (#684, #723 e altro) diff --git a/fastlane/metadata/android/it-IT/changelogs/2000.txt b/fastlane/metadata/android/it-IT/changelogs/2000.txt new file mode 100644 index 000000000..e068c3e7e --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/2000.txt @@ -0,0 +1,10 @@ +* aggiunto il supporto base ai tasti modificatori +* aggiunte funzioni di pressione prolungata su alcuni tasti della barra degli strumenti +* più tasti disponibili sulla barra degli strumenti della cronologia appunti +* barra degli strumenti della cronologia degli personalizzabile +* Personalizzazione di tutti i colori disponibili +* nuova opzione per mostrare sempre la parola da inserire come suggerimento centrale +* nuovo indicatore del blocco maiuscole +* aggiunta di layout piemontese, mari orientale e mansi; layout estesi per kannada e ungherese +* correzione del testo tagliato nel popup di anteprima dei tasti su alcuni dispositivi +* ulteriori correzioni e miglioramenti (vedi note di rilascio) diff --git a/fastlane/metadata/android/it-IT/changelogs/2001.txt b/fastlane/metadata/android/it-IT/changelogs/2001.txt new file mode 100644 index 000000000..31fe669f3 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/2001.txt @@ -0,0 +1,11 @@ +* personalizzazione dei layout dei tasti funzionali +* modifiche ai layout personalizzati 'Simboli' e 'Altri simboli' +* nuova opzione per mostrare/nascondere automaticamente la barra degli strumenti +* notifica toast alla copia del testo +* possibilità di separare le funzioni di cambio lingua e cambio tastiera +* popup con virgola per i layout numerici e telefonici +* opzione per rendere opzionale l'effetto del tap prolungato su un'icona della barra degli strumenti (fissa l'icona in modo che sia sempre visibile) +* nuova sezione delle impostazioni dedicata alla barra degli strumenti +* aggiunta del tasto tabulazione +* CTRL/CMD, barra degli strumenti e altre etichette dei tasti nei layout +* correzioni e miglioramenti minori diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/fastlane/metadata/android/it-IT/full_description.txt new file mode 100644 index 000000000..9224e2efa --- /dev/null +++ b/fastlane/metadata/android/it-IT/full_description.txt @@ -0,0 +1,29 @@ +HeliBoard è una tastiera open-source attenta alla privacy, basata su AOSP / OpenBoard. +Non ha bisogno dell'accesso alla rete, essendo 100% offline. + +Caratteristiche: +
      +
    • Dizionari multilingua per suggerimenti e controllo ortografico
    • +
        +
      • crea il tuo, oppure scarica quelli esistenti da qui, o dalla sezione sperimentale (la qualità può variare)
      • +
      • dizionari specializzati per emoji o simboli scientifici possono essere usati per fornire suggerimenti specifici
      • +
      • per i layout coreani, i suggerimenti funzionano solo usando questo dizionario; gli strumenti nel repository dei dizionari non sono in grado di creare dizionari funzionanti
      • +
      +
    • temi personalizzati (stile, colori, immagine di sfondo)
    • +
        +
      • attiva o disattiva la modalità scura di Android 10+ seguendo l'impostazione di sistema
      • +
      • colori dinamici per Android 12+
      • +
      +
    • Layout personalizzabili (disponibili se si disabilita usa lingue di sistema)
    • +
    • digitazione multilingue
    • +
    • digitazione a scorrimento (solo con libreria closed source ☹️)
    • +
        +
      • libreria non inclusa nell'app, poiché non è disponibile alcuna libreria open source compatibile
      • +
      • può essere estratta dai pacchetti GApps ("swypelibs"), o scaricata qui
      • +
      +
    • Cronologia degli appunti
    • +
    • Modalità a una mano
    • +
    • Tastiera divisa (disponibile solo se lo schermo è abbastanza grande)
    • +
    • Tastierino numerico
    • +
    • Backup e ripristino dei dati di apprendimento e della cronologia
    • +
    diff --git a/fastlane/metadata/android/it-IT/short_description.txt b/fastlane/metadata/android/it-IT/short_description.txt new file mode 100644 index 000000000..b6d1cf25c --- /dev/null +++ b/fastlane/metadata/android/it-IT/short_description.txt @@ -0,0 +1 @@ +Tastiera open source responsiva e altamente personalizzabile diff --git a/fastlane/metadata/android/it-IT/title.txt b/fastlane/metadata/android/it-IT/title.txt new file mode 100644 index 000000000..e9841ace0 --- /dev/null +++ b/fastlane/metadata/android/it-IT/title.txt @@ -0,0 +1 @@ +HeliBoard diff --git a/fastlane/metadata/android/nl-NL/changelogs/2001.txt b/fastlane/metadata/android/nl-NL/changelogs/2001.txt new file mode 100644 index 000000000..21ab1ad06 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/2001.txt @@ -0,0 +1,11 @@ +* De mogelijkheid om layout funtietoetsen aan te passen +* Aanpassing van Symbolen met meer lay-outs +* Opties voor automatisch weergeven/verbergen van werkbalk +* Pop-upmelding bij het kopiëren van tekst +* Scheiding van gedrag en activering van de taalkeuzetoets +* Pop-ups met kommatoetsen toegevoegd voor nummer- en telefoonlayouts +* Optionele mogelijkheid van lang indrukken in de werkbalk +* Werkbalkinstellingen in een aparte sectie +* Tab-toets toegevoegd +* Voorziening voor Ctrl, werkbalk en andere belangrijke labels in lay-outs +* kleine fixes en verbeteringen diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt index b4020441d..7a8a39a7b 100644 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -1,5 +1,5 @@ HeliBoard is een privacybewust open-source toetsenbord, gebaseerd op AOSP / OpenBoard. -Het vraagt geen netwerkrechten en is dus 100% offline. +Het verlangt geen netwerkrechten en is dus 100% offline. Kenmerken:
      @@ -14,16 +14,17 @@ Kenmerken:
    • Dag/nacht-instelling van het systeem volgen op Android 10+ (en op sommige versies van Android 9)
    • Dynamische kleuren volgen voor Android 12+
    • -
    • Toetsenbord layouts aanpassen (alleen beschikbaar bij het uitschakelen van gebruik systeemtalen )
    • +
    • Toetsenbord layouts aanpassen (alleen beschikbaar bij het uitschakelen van Systeemtalen gebruiken)
    • +
    • Aanpassing van speciale lay-outs, zoals symbolen, cijfers of functionele toetslay-outs
    • Meertalige invoer
    • -
    • Vegend typen (Glide typing) ( alleen m.b.v. gesloten bronbibliotheek ☹️)
    • +
    • Vegend typen (Glide typing) ( alleen m.b.v. gesloten bronbibliotheek ☹️)
      • bibliotheek niet opgenomen in de app, omdat er geen compatibele open source bibliotheek beschikbaar is
      • -
      • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier gedownload
      • +
      • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier downloaden (klik op het bestand en klik vervolgens op "raw" of de kleine download-knop)
      • Klembordgeschiedenis
      • Enkele handmodus
      • Gesplitst toetsenbord (alleen beschikbaar als het scherm groot genoeg is)
      • Numpad
      • -
      • Back-up en terugzetten van geleerde woorden / geschiedenisgegevens
      • +
      • Back-up en herstel van instellingen en geleerde woorden / geschiedenisgegevens
      • diff --git a/fastlane/metadata/android/pl-PL/changelogs/2001.txt b/fastlane/metadata/android/pl-PL/changelogs/2001.txt new file mode 100644 index 000000000..a35b587e8 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/2001.txt @@ -0,0 +1,11 @@ +* możliwość dostosowywania układów klawiszy funkcyjnych +* lekkie dostosowanie układów symboli i symboli dodatkowych +* dodano opcje automatycznego wyświetlania/ukrywania paska narzędzi +* dodano powiadomienie toast podczas kopiowania tekstu +* oddzielono opcję "zachowanie klawisza zmiany języka" od jego włączania +* dodano wyskakujące okienka do klawisza przecinka dla układów numerów i telefonu +* przypinanie poprzez długie naciśnięcie na pasku narzędzi jest teraz opcjonalne +* przeniesienie ustawień paska narzędzi do osobnej sekcji +* dodano klawisz tabulatora +* możliwość dodania ctrl, przycisków paska narzędzi oraz innych klawiszy do układów +* drobne poprawki i ulepszenia diff --git a/fastlane/metadata/android/pl-PL/full_description.txt b/fastlane/metadata/android/pl-PL/full_description.txt index 980cc13e4..43aa074b6 100644 --- a/fastlane/metadata/android/pl-PL/full_description.txt +++ b/fastlane/metadata/android/pl-PL/full_description.txt @@ -14,16 +14,17 @@ Funkcje:
      • może śledzić ustawienie dzień/noc w systemie Android 10+ (oraz w niektórych wersjach Androida 9)
      • może stosować dynamiczne kolory w systemie Android 12+
      -
    • Dostosuj układ klawiatury (możliwe tylko po wyłączeniu opcji użyj języków systemu)
    • +
    • Dostosuj układy klawiatury (możliwe tylko po wyłączeniu opcji użyj języków systemu)
    • +
    • Dostosuj układy specjalne, takie jak symbole, liczby lub układ klawiszy funkcyjnych
    • Pisanie wielojęzyczne
    • Pisanie gestami (tylko z zamkniętą biblioteką ☹️)
      • biblioteka nie znajduje się w aplikacji, ponieważ nie ma kompatybilnej biblioteki typu open source
      • -
      • można ją wyodrębnić z pakietów GApps ("swypelibs"), lub pobrać stąd
      • +
      • można ją wyodrębnić z pakietów GApps ("swypelibs"), lub pobrać stąd (kliknij plik, a następnie "raw" lub mały przycisk pobierania)
    • Historia schowka
    • Tryb jednej ręki
    • Podzielona klawiatura (jeśli ekran jest wystarczająco duży)
    • Klawiatura numeryczna
    • -
    • Tworzenie kopii zapasowych i przywracanie nauczonych słów / danych
    • +
    • Tworzenie kopii zapasowych i przywracanie ustawień oraz nauczonych słów/danych
    diff --git a/fastlane/metadata/android/ru-RU/title.txt b/fastlane/metadata/android/ru-RU/title.txt new file mode 100644 index 000000000..e9841ace0 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/title.txt @@ -0,0 +1 @@ +HeliBoard From 237adc991b8f17e1b4f88f5f01422ec149768a90 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 13 Jun 2024 20:08:40 +0200 Subject: [PATCH 30/92] properly update toolbar prefs --- .../main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index 76e9b1a32..a4019bc08 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -144,7 +144,6 @@ private fun upgradeToolbarPref(prefs: SharedPreferences, pref: String, default: if (!prefs.contains(pref)) return val list = prefs.getString(pref, default)!!.split(";").toMutableList() val splitDefault = defaultToolbarPref.split(";") - if (list.size == splitDefault.size) return splitDefault.forEach { entry -> val keyWithComma = entry.substringBefore(",") + "," if (list.none { it.startsWith(keyWithComma) }) @@ -159,7 +158,7 @@ private fun upgradeToolbarPref(prefs: SharedPreferences, pref: String, default: true } } - prefs.edit { putString(Settings.PREF_TOOLBAR_KEYS, list.joinToString(";")) } + prefs.edit { putString(pref, list.joinToString(";")) } } fun getEnabledToolbarKeys(prefs: SharedPreferences) = getEnabledToolbarKeys(prefs, Settings.PREF_TOOLBAR_KEYS, defaultToolbarPref) From d1c2ad0a39369e6e9d41f34f9eafb29d59f2e58a Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 13 Jun 2024 20:36:04 +0200 Subject: [PATCH 31/92] fix system sometimes enabling wrong subtype --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 5 +++++ app/src/main/res/xml/method_dummy.xml | 1 + 2 files changed, 6 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index bafd0f3f0..589fabc67 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -853,6 +853,11 @@ public void onFinishInput() { public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) { // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. + if (subtype.hashCode() == 0xf000000f) { + // For some reason sometimes the system wants to set the dummy subtype, which messes with the currently enabled subtype. + // Now that the dummy subtype has a fixed id, we can easily avoid enabling it. + return; + } InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype(); StatsUtils.onSubtypeChanged(oldSubtype, subtype); mRichImm.onSubtypeChanged(subtype); diff --git a/app/src/main/res/xml/method_dummy.xml b/app/src/main/res/xml/method_dummy.xml index ee1a48031..9f6471c33 100644 --- a/app/src/main/res/xml/method_dummy.xml +++ b/app/src/main/res/xml/method_dummy.xml @@ -24,6 +24,7 @@ the system picker crashes on some devices for unknown reasons. From 181ea3b586fd6302c7fb6c681c5da26ac88d4f1c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 16:32:50 +0200 Subject: [PATCH 32/92] disable shrinkResources because the build process seems to randomly ignore tools:keep --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 928869a3a..43a6986c0 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,14 +21,14 @@ android { buildTypes { release { minifyEnabled true - shrinkResources true + shrinkResources false debuggable false jniDebuggable false renderscriptDebuggable false } nouserlib { // same as release, but does not allow the user to provide a library minifyEnabled true - shrinkResources true + shrinkResources false debuggable false jniDebuggable false renderscriptDebuggable false From 61cff40d3c9aaf627843dc4a0641bbc3a235d594 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 17:08:49 +0200 Subject: [PATCH 33/92] only use alt-code-while-typing when not clearly inside key and add alt code to emoji and clipboard keys fixes #630 --- app/src/main/java/helium314/keyboard/keyboard/Key.java | 9 ++++++++- .../java/helium314/keyboard/keyboard/PointerTracker.java | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 087cca130..5368ede70 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -524,6 +524,13 @@ public final boolean noKeyPreview() { return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; } + /** + * altCodeWhileTyping is a weird thing. + * When user pressed a typing key less than ignoreAltCodeKeyTimeout (config_ignore_alt_code_key_timeout / 350 ms) ago, + * this code will be used instead. There is no documentation, but it appears the purpose is to avoid unintentional layout switches. + * Assuming this is true, the key still is used now if pressed near the center, where we assume it's less likely to be accidental. + * See PointerTracker.isClearlyInsideKey + */ public final boolean altCodeWhileTyping() { return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; } @@ -1165,7 +1172,7 @@ public KeyParams( mActionFlags = actionFlags; final int altCodeInAttr; // settings and language switch keys have alt code space, all others nothing - if (mCode == KeyCode.SETTINGS || mCode == KeyCode.LANGUAGE_SWITCH) + if (mCode == KeyCode.SETTINGS || mCode == KeyCode.LANGUAGE_SWITCH || mCode == KeyCode.EMOJI || mCode == KeyCode.CLIPBOARD) altCodeInAttr = Constants.CODE_SPACE; else altCodeInAttr = KeyCode.NOT_SPECIFIED; diff --git a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java index 4b35442e2..b08220422 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java @@ -270,7 +270,7 @@ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, final int y, final long eventTime, final boolean isKeyRepeat) { final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); - final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); + final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState() && !isClearlyInsideKey(key, x, y); final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { final String output = code == KeyCode.MULTIPLE_CODE_POINTS @@ -1186,6 +1186,12 @@ private int getLongPressTimeout(final int code) { return longpressTimeout; } + private boolean isClearlyInsideKey(final Key key, final int x, final int y) { + // less than 15% of width from edge + return x > key.getX() + key.getWidth() * 0.15 && x < key.getX() + key.getWidth() * 0.85 + && y > key.getY() + key.getHeight() * 0.15 && y < key.getY() + key.getHeight() * 0.85; + } + private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { if (key == null) { callListenerOnCancelInput(); From 3eafce9265b96b7778fc678152fdf0ef742974ce Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 17:44:32 +0200 Subject: [PATCH 34/92] update key labels remove emoji label, not necessary any more as it's a toolbar key access toolbar labels via enum map allow CLIPBOARD_CUT key(code) --- .../internal/keyboard_parser/KeyboardParser.kt | 4 +++- .../internal/keyboard_parser/floris/KeyCode.kt | 2 +- .../internal/keyboard_parser/floris/KeyLabel.kt | 7 ++++--- .../internal/keyboard_parser/floris/TextKeyData.kt | 12 ++++++------ .../helium314/keyboard/latin/utils/ToolbarUtils.kt | 3 ++- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt index db8610c76..b95994cc6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt @@ -19,10 +19,12 @@ import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT import helium314.keyboard.latin.utils.POPUP_KEYS_NUMBER import helium314.keyboard.latin.utils.ScriptUtils import helium314.keyboard.latin.utils.ScriptUtils.script +import helium314.keyboard.latin.utils.ToolbarKey import helium314.keyboard.latin.utils.removeFirst import helium314.keyboard.latin.utils.replaceFirst import helium314.keyboard.latin.utils.splitAt import helium314.keyboard.latin.utils.sumOf +import helium314.keyboard.latin.utils.toolbarKeyStrings /** * Abstract parser class that handles creation of keyboard from [KeyData] arranged in rows, @@ -199,7 +201,7 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co if (!Settings.getInstance().current.mHasCustomFunctionalLayout) { // remove keys that should only exist on specific layouts or depend on setting (emoji, numpad, language switch) if (!Settings.getInstance().current.mShowsEmojiKey || !params.mId.isAlphabetKeyboard) - functionalKeysBottom.removeFirst { it.label == KeyLabel.EMOJI } + functionalKeysBottom.removeFirst { it.label == toolbarKeyStrings[ToolbarKey.EMOJI] } if (!Settings.getInstance().current.isLanguageSwitchKeyEnabled || !params.mId.isAlphabetKeyboard) functionalKeysBottom.removeFirst { it.label == KeyLabel.LANGUAGE_SWITCH } if (params.mId.mElementId != KeyboardId.ELEMENT_SYMBOLS) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index 20f224c13..36c9229e5 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -144,7 +144,7 @@ object KeyCode { fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { // working CURRENCY_SLOT_1, CURRENCY_SLOT_2, CURRENCY_SLOT_3, CURRENCY_SLOT_4, CURRENCY_SLOT_5, CURRENCY_SLOT_6, - VOICE_INPUT, LANGUAGE_SWITCH, SETTINGS, DELETE, ALPHA, SYMBOL, EMOJI, CLIPBOARD, + VOICE_INPUT, LANGUAGE_SWITCH, SETTINGS, DELETE, ALPHA, SYMBOL, EMOJI, CLIPBOARD, CLIPBOARD_CUT, UNDO, REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_SELECT_ALL, CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT, FN, CLIPBOARD_CLEAR_HISTORY, diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt index 8e0905d4d..6d039aa46 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt @@ -1,10 +1,11 @@ package helium314.keyboard.keyboard.internal.keyboard_parser.floris import helium314.keyboard.keyboard.internal.KeyboardParams +import helium314.keyboard.latin.utils.ToolbarKey +import helium314.keyboard.latin.utils.toolbarKeyStrings /** labels for functional / special keys */ object KeyLabel { - const val EMOJI = "emoji" const val COM = "com" const val LANGUAGE_SWITCH = "language_switch" const val ACTION = "action" @@ -38,8 +39,8 @@ object KeyLabel { "view_numeric_advanced" -> NUMPAD "view_phone" -> ALPHA // phone keyboard is treated like alphabet, just with different layout "view_phone2" -> SYMBOL // phone symbols - "ime_ui_mode_media" -> EMOJI - "ime_ui_mode_clipboard" -> "clipboard" // todo: is this supported? when yes -> add to readme, and add a test + "ime_ui_mode_media" -> toolbarKeyStrings[ToolbarKey.EMOJI]!! + "ime_ui_mode_clipboard" -> toolbarKeyStrings[ToolbarKey.CLIPBOARD]!! "ime_ui_mode_text" -> ALPHA "currency_slot_1" -> CURRENCY "currency_slot_2" -> CURRENCY1 diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index bf2827dff..d34d589d3 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -427,8 +427,8 @@ sealed interface KeyData : AbstractKeyData { // functional keys when (label) { // or use code? KeyLabel.SYMBOL_ALPHA, KeyLabel.SYMBOL, KeyLabel.ALPHA, KeyLabel.COMMA, KeyLabel.PERIOD, KeyLabel.DELETE, - KeyLabel.EMOJI, KeyLabel.COM, KeyLabel.LANGUAGE_SWITCH, KeyLabel.NUMPAD, KeyLabel.CTRL, KeyLabel.ALT, - KeyLabel.FN, KeyLabel.META -> return Key.BACKGROUND_TYPE_FUNCTIONAL + KeyLabel.COM, KeyLabel.LANGUAGE_SWITCH, KeyLabel.NUMPAD, KeyLabel.CTRL, KeyLabel.ALT, + KeyLabel.FN, KeyLabel.META, toolbarKeyStrings[ToolbarKey.EMOJI] -> return Key.BACKGROUND_TYPE_FUNCTIONAL KeyLabel.SPACE, KeyLabel.ZWNJ -> return Key.BACKGROUND_TYPE_SPACEBAR KeyLabel.ACTION -> return Key.BACKGROUND_TYPE_ACTION KeyLabel.SHIFT -> return getShiftBackground(params) @@ -465,7 +465,7 @@ sealed interface KeyData : AbstractKeyData { KeyLabel.ACTION -> "${getActionKeyLabel(params)}|${getActionKeyCode(params)}" KeyLabel.DELETE -> "!icon/delete_key|!code/key_delete" KeyLabel.SHIFT -> "${getShiftLabel(params)}|!code/key_shift" - KeyLabel.EMOJI -> "!icon/emoji_normal_key|!code/key_emoji" +// KeyLabel.EMOJI -> "!icon/emoji_normal_key|!code/key_emoji" // todo (later): label and popupKeys for .com should be in localeKeyTexts, handled similar to currency key KeyLabel.COM -> ".com" KeyLabel.LANGUAGE_SWITCH -> "!icon/language_switch_key|!code/key_language_switch" @@ -480,7 +480,7 @@ sealed interface KeyData : AbstractKeyData { KeyLabel.CTRL, KeyLabel.ALT, KeyLabel.FN, KeyLabel.META -> label.uppercase(Locale.US) KeyLabel.TAB -> "!icon/tab_key|" else -> { - if (label in toolbarKeyStrings) { + if (label in toolbarKeyStrings.values) { "!icon/$label|" } else label } @@ -498,7 +498,7 @@ sealed interface KeyData : AbstractKeyData { KeyLabel.META -> KeyCode.META KeyLabel.TAB -> KeyCode.TAB else -> { - if (label in toolbarKeyStrings) { + if (label in toolbarKeyStrings.values) { getCodeForToolbarKey(ToolbarKey.valueOf(label.uppercase(Locale.US))) } else code } @@ -519,7 +519,7 @@ sealed interface KeyData : AbstractKeyData { } KeyLabel.SPACE -> if (params.mId.isNumberLayout) Key.LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM else 0 KeyLabel.SHIFT -> Key.LABEL_FLAGS_PRESERVE_CASE or if (!params.mId.isAlphabetKeyboard) Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR else 0 - KeyLabel.EMOJI -> KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId) + toolbarKeyStrings[ToolbarKey.EMOJI] -> KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId) KeyLabel.COM -> Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE KeyLabel.ZWNJ -> Key.LABEL_FLAGS_HAS_POPUP_HINT KeyLabel.CURRENCY -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index a4019bc08..dcb09294d 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -14,6 +14,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode import helium314.keyboard.latin.R import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.ToolbarKey.* +import java.util.EnumMap import java.util.Locale fun createToolbarKey(context: Context, keyboardAttr: TypedArray, key: ToolbarKey): ImageButton { @@ -114,7 +115,7 @@ enum class ToolbarKey { FULL_LEFT, FULL_RIGHT, INCOGNITO, AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY, EMOJI } -val toolbarKeyStrings: Set = entries.mapTo(HashSet()) { it.toString().lowercase(Locale.US) } +val toolbarKeyStrings = entries.associateWithTo(EnumMap(ToolbarKey::class.java)) { it.toString().lowercase(Locale.US) } val defaultToolbarPref = entries.filterNot { it == CLOSE_HISTORY }.joinToString(";") { when (it) { From 753e5fa6a236b7fac07d37795b2593cfaeb94e9c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 18:17:26 +0200 Subject: [PATCH 35/92] properly open json files not sure why it works now, while it apparently did not before --- .../helium314/keyboard/keyboard/internal/KeyboardBuilder.kt | 2 +- .../keyboard/latin/settings/LanguageSettingsFragment.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt index f528f0117..399b4963b 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt @@ -122,7 +122,7 @@ open class KeyboardBuilder(protected val mContext: Context, row.forEach { it.setAbsoluteDimensions(currentX, currentY) if (DebugFlags.DEBUG_ENABLED) - Log.d(TAG, "setting size and position for ${it.mLabel}, ${it.mCode}: x ${currentX.toInt()}, w ${it.mAbsoluteWidth.toInt()}") + Log.d(TAG, "setting size and position for ${it.mLabel ?: it.mIconName}, ${it.mCode}: x ${currentX.toInt()}, w ${it.mAbsoluteWidth.toInt()}") currentX += it.mAbsoluteWidth } currentY += row.first().mAbsoluteHeight diff --git a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsFragment.kt index 92d90bb53..3c3d0fa15 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsFragment.kt @@ -209,7 +209,7 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) .addCategory(Intent.CATEGORY_OPENABLE) // todo: any working way to allow only json and text files? - .putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/*", "application/octet-stream")) // doesn't allow opening json files with "application/json" + .putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/*", "application/octet-stream", "application/json")) .setType("*/*") layoutFilePicker.launch(intent) } From 5d0341195e6da42abd6576e33640d9e2650b1dba Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 18:26:42 +0200 Subject: [PATCH 36/92] fix newly added layout file not loaded --- .../helium314/keyboard/latin/settings/LanguageSettingsDialog.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt index d27f890d9..905d4660b 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt @@ -101,6 +101,7 @@ class LanguageSettingsDialog( } private fun addSubtype(name: String) { + onCustomLayoutFileListChanged() val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) From f7b12757ed3c72d3ac4cb790daa0095dceb2ab52 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 18:59:37 +0200 Subject: [PATCH 37/92] set correct icon when voice input key is on keyboard, but no voice input method is available --- .../java/helium314/keyboard/keyboard/Key.java | 15 ++++++++++++--- .../keyboard/keyboard/PopupKeysKeyboardView.java | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 5368ede70..6ba91d3cc 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -22,6 +22,8 @@ import helium314.keyboard.latin.common.Constants; import helium314.keyboard.latin.common.StringUtils; import helium314.keyboard.latin.utils.PopupKeysUtilsKt; +import helium314.keyboard.latin.utils.ToolbarKey; +import helium314.keyboard.latin.utils.ToolbarUtilsKt; import java.util.Arrays; import java.util.Locale; @@ -207,10 +209,11 @@ public Key(@Nullable final String label, @Nullable final String iconName, final mPopupKeys = null; mPopupKeysColumnAndFlags = 0; mLabel = label; - mOptionalAttributes = OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, null, 0, 0); mCode = code; mEnabled = (code != KeyCode.NOT_SPECIFIED); mIconName = iconName; + mOptionalAttributes = OptionalAttributes.newInstance(outputText, KeyCode.NOT_SPECIFIED, + mIconName == null ? null : getDisabledIconName(mIconName), 0, 0); // Horizontal gap is divided equally to both sides of the key. mX = x + mHorizontalGap / 2; mY = y; @@ -943,6 +946,12 @@ public boolean hasFunctionalBackground() { || mBackgroundType == BACKGROUND_TYPE_STICKY_ON; } + @Nullable private static String getDisabledIconName(@NonNull final String iconName) { + if (iconName.equals(ToolbarUtilsKt.getToolbarKeyStrings().get(ToolbarKey.VOICE))) + return KeyboardIconsSet.NAME_SHORTCUT_KEY_DISABLED; + return null; + } + public static class Spacer extends Key { private Spacer(KeyParams keyParams) { super(keyParams); @@ -1180,8 +1189,8 @@ public KeyParams( ? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing) : altCodeInAttr; mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, - // disabled icon only ever for old version of shortcut key, visual insets can be replaced with spacer - null, 0, 0); + // disabled icon only shortcut / voice key, visual insets can be replaced with spacer + mIconName == null ? null : getDisabledIconName(mIconName), 0, 0); // KeyVisualAttributes for a key essentially are what the theme has, but on a per-key base // could be used e.g. for having a color gradient on key color mKeyVisualAttributes = null; diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java index ad642ff48..7d1735c08 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java @@ -24,6 +24,7 @@ import helium314.keyboard.keyboard.internal.KeyDrawParams; import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; import helium314.keyboard.latin.R; +import helium314.keyboard.latin.RichInputMethodManager; import helium314.keyboard.latin.common.Constants; import helium314.keyboard.latin.common.CoordinateUtils; @@ -107,6 +108,11 @@ public void setKeyboard(@NonNull final Keyboard keyboard) { } else { mAccessibilityDelegate = null; } + final Key shortcutKey = keyboard.getKey(KeyCode.VOICE_INPUT); + if (shortcutKey != null) { + shortcutKey.setEnabled(RichInputMethodManager.getInstance().isShortcutImeReady()); + invalidateKey(shortcutKey); + } } /** From 17f30ad6d6d49addc08532f35e90ba45b9e2705d Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 14 Jun 2024 22:01:15 +0200 Subject: [PATCH 38/92] fix typo in split distace (6000 -> 600) and increase max split distance for high dp devices fixes #869 --- .../java/helium314/keyboard/latin/settings/SettingsValues.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index bfa8e6c64..0b37363f2 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -184,7 +184,7 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false) && displayWidthDp > 600; // require display width of 600 dp for split // determine spacerWidth from display width and scale setting mSplitKeyboardSpacerRelativeWidth = mIsSplitKeyboardEnabled - ? Math.min(Math.max((displayWidthDp - 600) / 6000f + 0.15f, 0.15f), 0.25f) * prefs.getFloat(Settings.PREF_SPLIT_SPACER_SCALE, DEFAULT_SIZE_SCALE) + ? Math.min(Math.max((displayWidthDp - 600) / 600f + 0.15f, 0.15f), 0.35f) * prefs.getFloat(Settings.PREF_SPLIT_SPACER_SCALE, DEFAULT_SIZE_SCALE) : 0f; mQuickPinToolbarKeys = prefs.getBoolean(Settings.PREF_QUICK_PIN_TOOLBAR_KEYS, false); mScreenMetrics = Settings.readScreenMetrics(res); From c064ba199c2558be73665280c5981e09a7b628c1 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 15 Jun 2024 21:54:47 +0200 Subject: [PATCH 39/92] move that special handling for emoji and language switch keys into layouts for that, a new keyboard_state_selector is added this allows modifying functional key layouts without having to manually adjust those keys --- README.md | 1 + .../main/assets/layouts/functional_keys.json | 6 +- .../keyboard_parser/KeyboardParser.kt | 11 +--- .../keyboard_parser/RawKeyboardParser.kt | 2 + .../keyboard_parser/floris/KeyData.kt | 59 ++++++++++++++++--- layouts.md | 4 +- 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index bcbfa65f4..04cb21585 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Features that may go unnoticed, and further potentially useful information * Select text and press shift to switch between uppercase, lowercase and capitalize words * You can add dictionaries by opening the file * This only works with _content-uris_ and not with _file-uris_, meaning that it may not work with some file explorers. +* Not really a feature, but you can restart the keyboard by going to the settings and swiping it away from recents * _Debug mode / debug APK_ * Long-press a suggestion in the suggestion strip twice to show the source dictionary. * When using debug APK, you can find _Debug Settings_ within the _Advanced Preferences_, though the usefulness is limited except for dumping dictionaries into the log. diff --git a/app/src/main/assets/layouts/functional_keys.json b/app/src/main/assets/layouts/functional_keys.json index 858309014..6d28dd24f 100644 --- a/app/src/main/assets/layouts/functional_keys.json +++ b/app/src/main/assets/layouts/functional_keys.json @@ -11,9 +11,9 @@ "email": { "label": "@", "groupId": 1, "type": "function" }, "uri": { "label": "/", "groupId": 1, "type": "function" } }, - { "label": "language_switch" }, - { "label": "emoji" }, - { "label": "numpad" }, + { "$": "keyboard_state_selector", "languageKeyEnabled": { "$": "keyboard_state_selector", "alphabet": { "label": "language_switch" }}}, + { "$": "keyboard_state_selector", "emojiKeyEnabled": { "$": "keyboard_state_selector", "alphabet": { "label": "emoji" }}}, + { "$": "keyboard_state_selector", "symbols": { "label": "numpad" }}, { "label": "space" }, { "label": "period", "labelFlags": 1073741824 }, { "label": "action", "width": 0.15 } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt index b95994cc6..c74991d7f 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/KeyboardParser.kt @@ -198,16 +198,7 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co val functionalKeysBottom = allFunctionalKeys.lastOrNull() ?: return if (!params.mId.isAlphaOrSymbolKeyboard || functionalKeysBottom.isEmpty() || functionalKeysBottom.any { it.isKeyPlaceholder() }) return - if (!Settings.getInstance().current.mHasCustomFunctionalLayout) { - // remove keys that should only exist on specific layouts or depend on setting (emoji, numpad, language switch) - if (!Settings.getInstance().current.mShowsEmojiKey || !params.mId.isAlphabetKeyboard) - functionalKeysBottom.removeFirst { it.label == toolbarKeyStrings[ToolbarKey.EMOJI] } - if (!Settings.getInstance().current.isLanguageSwitchKeyEnabled || !params.mId.isAlphabetKeyboard) - functionalKeysBottom.removeFirst { it.label == KeyLabel.LANGUAGE_SWITCH } - if (params.mId.mElementId != KeyboardId.ELEMENT_SYMBOLS) - functionalKeysBottom.removeFirst { it.label == KeyLabel.NUMPAD } - } - // replace comma / period if 2 keys in normal bottom row + // replace comma / period if 2 keys in normal bottom row if (baseKeys.last().size == 2) { functionalKeysBottom.replaceFirst( { it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA}, diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt index 37b532b88..69abe164a 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt @@ -11,6 +11,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.CaseSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.CharWidthSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KanaSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyData +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyboardStateSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.LayoutDirectionSelector import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData import helium314.keyboard.keyboard.internal.keyboard_parser.floris.ShiftStateSelector @@ -171,6 +172,7 @@ object RawKeyboardParser { subclass(CaseSelector::class, CaseSelector.serializer()) subclass(ShiftStateSelector::class, ShiftStateSelector.serializer()) subclass(VariationSelector::class, VariationSelector.serializer()) + subclass(KeyboardStateSelector::class, KeyboardStateSelector.serializer()) subclass(LayoutDirectionSelector::class, LayoutDirectionSelector.serializer()) subclass(CharWidthSelector::class, CharWidthSelector.serializer()) subclass(KanaSelector::class, KanaSelector.serializer()) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt index c9d53acbf..7c8b0736d 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyData.kt @@ -9,7 +9,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import helium314.keyboard.keyboard.KeyboardId import helium314.keyboard.keyboard.internal.KeyboardParams -import helium314.keyboard.latin.common.StringUtils // taken from FlorisBoard, small modifications // popup not nullable (maybe change back, but currently that's necessary for number keys) @@ -131,9 +130,7 @@ class ShiftStateSelector( } /** - * Allows to select an [AbstractKeyData] based on the current variation. Note that this type of selector only really - * makes sense in a text context, though technically speaking it can be used anywhere, so this implementation allows - * for any [AbstractKeyData] to be used here. The JSON class identifier for this selector is `variation_selector`. + * Allows to select an [AbstractKeyData] based on the current variation. The JSON class identifier for this selector is `variation_selector`. * * Example usage in a layout JSON file: * ``` @@ -155,6 +152,12 @@ class ShiftStateSelector( * [default] will be used instead. * @property password The key data to use if [KeyboardId.passwordInput] return true. If this value is * null, [default] will be used instead. + * @property date The key data to use if [KeyboardId.MODE_DATE] is active. If this value is null, + * null, [default] will be used instead. + * @property time The key data to use if [KeyboardId.MODE_TIME] is active. If this value is null, + * null, [default] will be used instead. + * @property datetime The key data to use if [KeyboardId.MODE_DATETIME] is active. If this value is null, + * null, [default] will be used instead. */ @Serializable @SerialName("variation_selector") @@ -170,16 +173,13 @@ data class VariationSelector( ) : AbstractKeyData { override fun compute(params: KeyboardParams): KeyData? { return when { - // todo: what is normal and all? -// KeyVariation.ALL -> default -// KeyVariation.NORMAL -> normal ?: default params.mId.passwordInput() -> password ?: default params.mId.mMode == KeyboardId.MODE_EMAIL -> email ?: default params.mId.mMode == KeyboardId.MODE_URL -> uri ?: default params.mId.mMode == KeyboardId.MODE_DATE -> date ?: default params.mId.mMode == KeyboardId.MODE_TIME -> time ?: default params.mId.mMode == KeyboardId.MODE_DATETIME -> datetime ?: default - else -> default + else -> normal ?: default }?.compute(params) } @@ -188,6 +188,49 @@ data class VariationSelector( } } +/** + * Allows to select an [AbstractKeyData] based on states saved in [KeyboardId]. + * The JSON class identifier for this selector is `keyboard_state_selector`. + * Note that the conditions are checked in order as given below, and the first non-null AbstractKeyData is selected. + * + * @property emojiKeyEnabled The key data to use if [KeyboardId.mEmojiKeyEnabled] is true. + * @property languageKeyEnabled The key data to use if [KeyboardId.mLanguageSwitchKeyEnabled] is true. + * @property symbols The key data to use if [KeyboardId.mElementId] is [KeyboardId.ELEMENT_SYMBOLS]. + * @property moreSymbols The key data to use if [KeyboardId.mElementId] is [KeyboardId.ELEMENT_SYMBOLS_SHIFTED]. + * @property alphabet The key data to use if [KeyboardId.isAlphabetKeyboard] is true. + * @property default The default key data which should be used in case none of the other conditions have a matching non-null + * AbstractKeyData. Can be null, in this case no key is displayed. + */ +@Serializable +@SerialName("keyboard_state_selector") +class KeyboardStateSelector( + val emojiKeyEnabled: AbstractKeyData? = null, + val languageKeyEnabled: AbstractKeyData? = null, + val symbols: AbstractKeyData? = null, + val moreSymbols: AbstractKeyData? = null, + val alphabet: AbstractKeyData? = null, + val default: AbstractKeyData? = null, +) : AbstractKeyData { + override fun compute(params: KeyboardParams): KeyData? { + if (params.mId.mEmojiKeyEnabled) + emojiKeyEnabled?.compute(params)?.let { return it } + if (params.mId.mLanguageSwitchKeyEnabled) + languageKeyEnabled?.compute(params)?.let { return it } + if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) + symbols?.compute(params)?.let { return it } + if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) + moreSymbols?.compute(params)?.let { return it } + if (params.mId.isAlphabetKeyboard) + alphabet?.compute(params)?.let { return it } + + return default?.compute(params) + } + + override fun asString(isForDisplay: Boolean): String { + return "" + } +} + /** * Allows to select an [AbstractKeyData] based on the current layout direction. Note that this type of selector only * really makes sense in a text context, though technically speaking it can be used anywhere, so this implementation diff --git a/layouts.md b/layouts.md index c73614710..958942cdf 100644 --- a/layouts.md +++ b/layouts.md @@ -33,7 +33,9 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm * there are also selector classes, which allow to change keys conditionally, see the [dvorak layout](https://github.com/Helium314/HeliBoard/blob/main/app/src/main/assets/layouts/dvorak.json) for an example: * `case_selector`: keys for `lower` and `upper` (both mandatory), similar to `shift_state_selector` * `shift_state_selector`: keys for `unshifted`, `shifted`, `shiftedManual`, `shiftedAutomatic`, `capsLock`, `manualOrLocked`, `default` (all optional) - * `variation_selector`: keys for `datetime`, `time`, `date`, `password`, `normal`, `uri`, `email`, `default` (all optional) + * `variation_selector`: keys for input types `datetime`, `time`, `date`, `password`, `normal`, `uri`, `email`, `default` (all optional) + * `keyboard_state_selector`: keys for `emojiKeyEnabled`, `languageKeyEnabled`, `symbols`, `moreSymbols`, `alphabet`, `default` (all optional) + * the `keyEnabled` keys will be used if the corresponding setting is enabled, `symbols`, `moreSymbols`, `alphabet` will be used when the said keyboard view is active * `layout_direction_selector`: keys for `ltr` and `rtl` (both mandatory) ### Properties * A (non-selector) key can have the following properties: From 0da6f6e4da4d5c9d4c11c064ee22dae90b4035ed Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 15 Jun 2024 22:06:12 +0200 Subject: [PATCH 40/92] Revert "adjust alphabet for AlphabetIndexer" This reverts commit bc1e4d52a826d1f7527ea679d44da25afbc382b8. see discussion in #803: the commit makes things worse --- .../keyboard/latin/settings/UserDictionarySettings.java | 3 +-- app/src/main/res/values-pl/strings.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java b/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java index 53fd5210b..f019286e6 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/UserDictionarySettings.java @@ -33,7 +33,6 @@ import helium314.keyboard.latin.R; import helium314.keyboard.latin.common.LocaleUtils; -import helium314.keyboard.latin.utils.RunInLocaleKt; import java.util.Locale; @@ -324,7 +323,7 @@ public MyAdapter(final Context context, final int layout, final Cursor c, super(context, layout, c, from, to, 0 /* flags */); if (null != c) { - final String alphabet = RunInLocaleKt.runInLocale(context, mLocale, (ctx) -> ctx.getString(R.string.user_dict_fast_scroll_alphabet)); + final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet); final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); } diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5222ad441..c1bcddf9c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -117,7 +117,7 @@ "Brak słów w słowniku użytkownika. Aby dodać słowo, kliknij przycisk Dodaj (+)." "Dla wszystkich języków" "Więcej języków…" - " ABCDEĘFGHIJKLŁMNOÓPQRSŚTUVWXYZŻ" + " ABCDEFGHIJKLMNOPQRSTUVWXYZ" Korekty Różne %s min. From 3961060710cf7202f9b832319abb01f22cdd293e Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 15 Jun 2024 22:28:18 +0200 Subject: [PATCH 41/92] add logging that should help with #829 --- .../java/helium314/keyboard/latin/DictionaryFacilitatorImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.java b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.java index 9e71ef21f..cfec22ec4 100644 --- a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.java +++ b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.java @@ -336,6 +336,7 @@ public void resetDictionaries( final HashMap> existingDictionariesToCleanup = new HashMap<>(); final HashSet subDictTypesToUse = new HashSet<>(); subDictTypesToUse.add(Dictionary.TYPE_USER); + Log.i(TAG, "resetDictionaries, force reloading main dictionary: " + forceReloadMainDictionary); final List allLocales = new ArrayList<>() {{ add(newLocale); addAll(Settings.getSecondaryLocales(DeviceProtectedUtils.getSharedPreferences(context), newLocale)); From 4ab7e8b78e45535f9e3d09cc5362353d4f8636c8 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 17 Jun 2024 15:12:05 +0200 Subject: [PATCH 42/92] use fallback layout when custom layout file can't be opened --- .../internal/keyboard_parser/RawKeyboardParser.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt index 69abe164a..669bfc5c1 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/RawKeyboardParser.kt @@ -21,6 +21,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey import helium314.keyboard.latin.common.splitOnWhitespace import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX +import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.ScriptUtils import helium314.keyboard.latin.utils.ScriptUtils.script import helium314.keyboard.latin.utils.getCustomFunctionalLayoutName @@ -32,6 +33,7 @@ import kotlinx.serialization.modules.polymorphic import java.io.File object RawKeyboardParser { + private const val TAG = "RawKeyboardParser" private val rawLayoutCache = hashMapOf MutableList>>() val symbolAndNumberLayouts = listOf(LAYOUT_SYMBOLS, LAYOUT_SYMBOLS_SHIFTED, LAYOUT_SYMBOLS_ARABIC, @@ -85,8 +87,15 @@ object RawKeyboardParser { private fun createCacheLambda(layoutName: String, context: Context): (KeyboardParams) -> MutableList> { val layoutFileName = getLayoutFileName(layoutName, context) - val layoutText = if (layoutFileName.startsWith(CUSTOM_LAYOUT_PREFIX)) getCustomLayoutFile(layoutFileName, context).readText() - else context.assets.open("layouts${File.separator}$layoutFileName").reader().use { it.readText() } + val layoutText = if (layoutFileName.startsWith(CUSTOM_LAYOUT_PREFIX)) { + try { + getCustomLayoutFile(layoutFileName, context).readText() + } catch (e: Exception) { // fall back to defaults if for some reason file is broken + val name = if (layoutName.contains("functional")) "functional_keys.json" else "qwerty.txt" + Log.e(TAG, "cannot open layout $layoutName, falling back to $name", e) + context.assets.open("layouts${File.separator}$name").reader().use { it.readText() } + } + } else context.assets.open("layouts${File.separator}$layoutFileName").reader().use { it.readText() } if (layoutFileName.endsWith(".json")) { val florisKeyData = parseJsonString(layoutText) return { params -> From 9efb22bd0c5c9a76d9429b4b825a80cc1dec4fb4 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 17 Jun 2024 16:05:22 +0200 Subject: [PATCH 43/92] fix #881 and improve related tests --- .../keyboard_parser/floris/TextKeyData.kt | 7 +-- .../helium314/keyboard/KeyboardParserTest.kt | 52 ++++++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index d34d589d3..5c01ed8f0 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -298,9 +298,10 @@ sealed interface KeyData : AbstractKeyData { fun getPopupLabel(params: KeyboardParams): String { val newLabel = processLabel(params) if (code == KeyCode.UNSPECIFIED) { - return if (newLabel == label) label - else if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys - else "${newLabel}|!code/${processCode()}" + if (newLabel == label) return label + val newCode = processCode() + if (newLabel.endsWith("|")) return "${newLabel}!code/$newCode" // for toolbar keys + return if (newCode == code) newLabel else "${newLabel}|!code/$newCode" } if (code >= 32) return "${newLabel}|${StringUtils.newSingleCodePointString(code)}" diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 9af660067..4929237a2 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -127,30 +127,30 @@ f""", // no newline at the end params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT) addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL) - data class Expected(val label: String?, val text: String?, val code: Int, val popups: List? = null) + data class Expected(val label: String?, val icon: String?, val text: String?, val code: Int, val popups: List>? = null) val expected = listOf( - Expected("a", null, 'a'.code, null), - Expected("a", null, 'a'.code, null), - Expected("a", null, 'b'.code, listOf("b")), // todo: should also check whether code is "a" - Expected("$", null, '$'.code, listOf("£", "€", "¢", "¥", "₱")), - Expected("$", null, '¥'.code, listOf("£", "€", "¢", "¥", "₱")), - Expected("i", null, 105, null), - Expected("্র", "্র", KeyCode.MULTIPLE_CODE_POINTS, null), - Expected("x", "্র", KeyCode.MULTIPLE_CODE_POINTS, null), - Expected(";", null, ';'.code, listOf(":")), - Expected(".", null, '.'.code, listOf(">")), - Expected("'", null, '\''.code, listOf("!", "\"")), - Expected("9", null, '9'.code, null), // todo (later): also should have different background or whatever is related to type - Expected(null, null, -7, null), // todo: expect an icon - Expected("?123", "?123", -202, null), - Expected(null, null, ' '.code, null), - Expected("(", null, '('.code, listOf("<", "[", "{")), - Expected("$", null, '$'.code, listOf("£", "₱", "€", "¢", "¥", "¥")), - Expected("a", null, ' '.code, null), - Expected("a", null, ' '.code, null), - Expected(null, null, KeyCode.CLIPBOARD, null), // todo: expect an icon - Expected(null, null, KeyCode.MULTIPLE_CODE_POINTS, null), // todo: this works here, but crashes on phone - Expected("p", null, 'p'.code, null), + Expected("a", null, null, 'a'.code, null), + Expected("a", null, null, 'a'.code, null), + Expected("a", null, null, 'b'.code, listOf("b" to 'a'.code)), + Expected("$", null, null, '$'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }), + Expected("$", null, null, '¥'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }), + Expected("i", null, null, 105, null), + Expected("্র", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null), + Expected("x", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null), + Expected(";", null, null, ';'.code, listOf(":").map { it to it.first().code }), + Expected(".", null, null, '.'.code, listOf(">").map { it to it.first().code }), + Expected("'", null, null, '\''.code, listOf("!", "\"").map { it to it.first().code }), + Expected("9", null, null, '9'.code, null), // todo (later): also should have different background or whatever is related to type + Expected(null, "delete_key", null, -7, null), + Expected("?123", null, "?123", -202, null), + Expected(null, "space_key", null, ' '.code, null), + Expected("(", null, null, '('.code, listOf("<", "[", "{").map { it to it.first().code }), + Expected("$", null, null, '$'.code, listOf("£" to '£'.code, "₱" to '₱'.code, "€" to '€'.code, "¢" to '¢'.code, "¥" to '¥'.code, "¥" to '€'.code)), + Expected("a", null, null, ' '.code, null), + Expected("a", null, null, ' '.code, null), + Expected(null, "clipboard_action_key", null, KeyCode.CLIPBOARD, null), + Expected(null, "clipboard_action_key", null, KeyCode.MULTIPLE_CODE_POINTS, null), // todo: this works here, but crashes on phone + Expected("p", null, null, 'p'.code, listOf("$" to '$'.code)), ) val layoutString = """ [ @@ -242,7 +242,7 @@ f""", // no newline at the end { "code": 32, "label": "a|b" }, { "label": "!icon/clipboard_action_key|!code/key_clipboard" }, { "label": "!icon/clipboard_action_key" }, - { "label": "p" } + { "label": "p", "popup": { "main": { "label": "$$$" } } } ], [ { "label": "q" }, @@ -278,8 +278,10 @@ f""", // no newline at the end val keyParams = keyData.toKeyParams(params) println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}") assertEquals(expected[index].label, keyParams.mLabel) + assertEquals(expected[index].icon, keyParams.mIconName) assertEquals(expected[index].code, keyParams.mCode) - assertEquals(expected[index].popups?.sorted(), keyParams.mPopupKeys?.mapNotNull { it.mLabel }?.sorted()) // todo (later): what's wrong with order? + // todo (later): what's wrong with popup order? + assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first }) assertEquals(expected[index].text, keyParams.outputText) } assertEquals("!", keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mLabel) From 0b9fb7334d2457420c462a35a01bb115776501d2 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 18 Jun 2024 00:38:07 +0200 Subject: [PATCH 44/92] reload the cursor position from text field when in doubt --- .../helium314/keyboard/latin/LatinIME.java | 2 +- .../keyboard/latin/RichInputConnection.java | 31 ++++++++++++++----- .../keyboard/latin/inputlogic/InputLogic.java | 8 ++--- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 589fabc67..5eb7665a6 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -997,7 +997,7 @@ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restart // didn't move (the keyboard having been closed with the back key), // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to // work around this bug. - mInputLogic.mConnection.tryFixLyingCursorPosition(); + mInputLogic.mConnection.tryFixIncorrectCursorPosition(); if (mInputLogic.mConnection.isCursorTouchingWord(currentSettingsValues.mSpacingAndPunctuations, true)) { mHandler.postResumeSuggestions(true /* shouldDelay */); } diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 87e699fe6..1bbd40a52 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -227,14 +227,21 @@ public void endBatchEdit() { */ public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart, final int newSelEnd, final boolean shouldFinishComposition) { - mExpectedSelStart = newSelStart; - mExpectedSelEnd = newSelEnd; mComposingText.setLength(0); final boolean didReloadTextSuccessfully = reloadTextCache(); if (!didReloadTextSuccessfully) { Log.d(TAG, "Will try to retrieve text later."); + // selection is set to INVALID_CURSOR_POSITION if reloadTextCache return false return false; } + if (mExpectedSelStart != newSelStart || mExpectedSelEnd != newSelEnd) { + mExpectedSelStart = newSelStart; + mExpectedSelEnd = newSelEnd; + reloadTextCache(); + if (mExpectedSelStart != newSelStart || mExpectedSelEnd != newSelEnd) { + Log.i(TAG, "resetCachesUponCursorMove: tried to set "+newSelStart+"/"+newSelEnd+", but input field has "+mExpectedSelStart+"/"+mExpectedSelEnd); + } + } if (isConnected() && shouldFinishComposition) { mIC.finishComposingText(); } @@ -272,6 +279,14 @@ private boolean reloadTextCache() { return true; } + private void reloadCursorPosition() { + if (!isConnected()) return; + final ExtractedText et = mIC.getExtractedText(new ExtractedTextRequest(), 0); + if (et == null) return; + mExpectedSelStart = et.selectionStart + et.startOffset; + mExpectedSelEnd = et.selectionEnd + et.startOffset; + } + private void checkBatchEdit() { if (mNestLevel != 1) { // TODO: exception instead @@ -458,10 +473,7 @@ public int getCharBeforeBeforeCursor() { if (result != null) { if (!checkTextBeforeCursorConsistency(result)) { Log.w(TAG, "cached text out of sync, reloading"); - ExtractedTextRequest r = new ExtractedTextRequest(); - final ExtractedText et = mIC.getExtractedText(r, 0); - mExpectedSelStart = et.selectionStart + et.startOffset; - mExpectedSelEnd = et.selectionEnd + et.startOffset; + reloadCursorPosition(); if (!DebugLogUtils.getStackTrace(2).contains("reloadTextCache")) // clunky bur effective protection against circular reference reloadTextCache(); } @@ -1130,8 +1142,10 @@ public boolean isInsideDoubleQuoteOrAfterDigit() { * means to get the real value, try at least to ask the text view for some characters and * detect the most damaging cases: when the cursor position is declared to be much smaller * than it really is. + * (renamed the method, because we clearly ask the editorInfo to provide initial selection, no reason to complain about it + * being initial and thus possibly outdated) */ - public void tryFixLyingCursorPosition() { + public void tryFixIncorrectCursorPosition() { mIC = mParent.getCurrentInputConnection(); final CharSequence textBeforeCursor = getTextBeforeCursor( Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); @@ -1167,6 +1181,9 @@ public void tryFixLyingCursorPosition() { if (wasEqual || mExpectedSelStart > mExpectedSelEnd) { mExpectedSelEnd = mExpectedSelStart; } + } else { + // better re-read the correct position instead of guessing from incomplete data + reloadCursorPosition(); } } } diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 2c7b2b248..272424b66 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -148,9 +148,9 @@ public void startInput(final String combiningSpec, final SettingsValues settings mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once mCurrentlyPressedHardwareKeys.clear(); mSuggestedWords = SuggestedWords.getEmptyInstance(); - // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying - // so we try using some heuristics to find out about these and fix them. - mConnection.tryFixLyingCursorPosition(); + // In some cases (e.g. after rotation of the device, or when scrolling the text before bringing up keyboard) + // editorInfo.initialSelStart is not the actual cursor position, so we try using some heuristics to find the correct position. + mConnection.tryFixIncorrectCursorPosition(); cancelDoubleSpacePeriodCountdown(); if (InputLogicHandler.NULL_HANDLER == mInputLogicHandler) { mInputLogicHandler = new InputLogicHandler(mLatinIME, this); @@ -2380,7 +2380,7 @@ public boolean retryResetCachesAndReturnSuccess(final boolean tryResumeSuggestio // If remainingTries is 0, we should stop waiting for new tries, however we'll still // return true as we need to perform other tasks (for example, loading the keyboard). } - mConnection.tryFixLyingCursorPosition(); + mConnection.tryFixIncorrectCursorPosition(); if (tryResumeSuggestions) { handler.postResumeSuggestions(true /* shouldDelay */); } From 5b7f4dae4c74c5f8999d739bc721874af297c475 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 18 Jun 2024 17:51:03 +0200 Subject: [PATCH 45/92] clarify error messages when layout can't be saved, see #887 --- .../keyboard/latin/utils/CustomLayoutUtils.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt index 68bc7710e..523575461 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt @@ -114,16 +114,28 @@ private fun checkKeys(keys: List>): Boolean { Log.w(TAG, "too many keys in one row") return false } - if (keys.any { row -> row.any { ((it.mLabel?.length ?: 0) > 20) } }) { - Log.w(TAG, "too long text on key") + if (keys.any { row -> row.any { + if ((it.mLabel?.length ?: 0) > 20) { + Log.w(TAG, "too long text on key: ${it.mLabel}") + true + } else false + } }) { return false } - if (keys.any { row -> row.any { (it.mPopupKeys?.size ?: 0) > 20 } }) { - Log.w(TAG, "too many popup keys on a key") + if (keys.any { row -> row.any { + if ((it.mPopupKeys?.size ?: 0) > 20) { + Log.w(TAG, "too many popup keys on key ${it.mLabel}") + true + } else false + } }) { return false } - if (keys.any { row -> row.any { it.mPopupKeys?.any { popupKey -> (popupKey.mLabel?.length ?: 0) > 10 } == true } }) { - Log.w(TAG, "too long text on popup key") + if (keys.any { row -> row.any { true == it.mPopupKeys?.any { popupKey -> + if ((popupKey.mLabel?.length ?: 0) > 10) { + Log.w(TAG, "too long text on popup key: ${popupKey.mLabel}") + true + } else false + } } }) { return false } return true From 3e74a29f2ef8d25594a75fd898f0b3d30f7128dd Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 19 Jun 2024 22:42:36 +0200 Subject: [PATCH 46/92] improve popup key handling and update tests remove outdated part from layouts.md fixes #883 --- .../java/helium314/keyboard/keyboard/Key.java | 2 +- .../keyboard/internal/KeySpecParser.java | 6 +- .../keyboard/internal/PopupKeySpec.java | 2 +- .../keyboard_parser/floris/TextKeyData.kt | 17 +- .../latin/PunctuationSuggestions.java | 2 +- .../keyboard/latin/utils/CustomLayoutUtils.kt | 4 +- .../helium314/keyboard/KeyboardParserTest.kt | 388 ++++++++++++------ layouts.md | 2 - 8 files changed, 289 insertions(+), 134 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 6ba91d3cc..9353546cf 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -1130,7 +1130,7 @@ public KeyParams( : hintLabel; } - String outputText = KeySpecParser.getOutputText(keySpec); + String outputText = KeySpecParser.getOutputText(keySpec, code); if (needsToUpcase) { outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing); } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java index aa5c8ae68..1a1388518 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeySpecParser.java @@ -150,7 +150,7 @@ private static String getOutputTextInternal(@NonNull final String keySpec, final } @Nullable - public static String getOutputText(@Nullable final String keySpec) { + public static String getOutputText(@Nullable final String keySpec, final int code) { if (keySpec == null) { // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory. return null; @@ -170,7 +170,9 @@ public static String getOutputText(@Nullable final String keySpec) { return outputText; } final String label = getLabel(keySpec); - if (label == null && DebugFlags.DEBUG_ENABLED) { + if (label == null) { + if (keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON) && code != KeyCode.UNSPECIFIED && code != KeyCode.MULTIPLE_CODE_POINTS) + return null; // allow empty label in case of icon & actual code throw new KeySpecParserError("Empty label: " + keySpec); } // Code is automatically generated for one letter label. See {@link getCode()}. diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java b/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java index 21f862673..c39eaa6f6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/PopupKeySpec.java @@ -60,7 +60,7 @@ public PopupKeySpec(@NonNull final String popupKeySpec, boolean needsToUpperCase mOutputText = mLabel; } else { mCode = code; - final String outputText = KeySpecParser.getOutputText(popupKeySpec); + final String outputText = KeySpecParser.getOutputText(popupKeySpec, code); mOutputText = needsToUpperCase ? StringUtils.toTitleCaseOfKeyLabel(outputText, locale) : outputText; } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 5c01ed8f0..fef79526f 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.Transient import helium314.keyboard.keyboard.Key import helium314.keyboard.keyboard.KeyboardId import helium314.keyboard.keyboard.KeyboardTheme +import helium314.keyboard.keyboard.internal.KeySpecParser import helium314.keyboard.keyboard.internal.KeyboardIconsSet import helium314.keyboard.keyboard.internal.KeyboardParams import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode @@ -303,13 +304,21 @@ sealed interface KeyData : AbstractKeyData { if (newLabel.endsWith("|")) return "${newLabel}!code/$newCode" // for toolbar keys return if (newCode == code) newLabel else "${newLabel}|!code/$newCode" } - if (code >= 32) - return "${newLabel}|${StringUtils.newSingleCodePointString(code)}" + if (code >= 32) { + if (newLabel.startsWith(KeyboardIconsSet.PREFIX_ICON)) { + // we ignore everything after the first | + // todo (later): for now this is fine, but it should rather be done when creating the popup key, + // and it should be consistent with other popups and also with normal keys + return "${newLabel.substringBefore("|")}|${StringUtils.newSingleCodePointString(code)}" + } + return "$newLabel|${StringUtils.newSingleCodePointString(code)}" + + } if (code in KeyCode.Spec.CURRENCY) { return getCurrencyLabel(params) } - return if (newLabel.endsWith("|")) "${newLabel}!code/${processCode()}" // for toolbar keys - else "${newLabel}|!code/${processCode()}" + return if (newLabel.endsWith("|")) "$newLabel!code/${processCode()}" // for toolbar keys + else "$newLabel|!code/${processCode()}" } fun getCurrencyLabel(params: KeyboardParams): String { diff --git a/app/src/main/java/helium314/keyboard/latin/PunctuationSuggestions.java b/app/src/main/java/helium314/keyboard/latin/PunctuationSuggestions.java index 6f6e75413..48be9ca91 100644 --- a/app/src/main/java/helium314/keyboard/latin/PunctuationSuggestions.java +++ b/app/src/main/java/helium314/keyboard/latin/PunctuationSuggestions.java @@ -64,7 +64,7 @@ public String getWord(final int index) { final String keySpec = super.getWord(index); final int code = KeySpecParser.getCode(keySpec); return (code == KeyCode.MULTIPLE_CODE_POINTS) - ? KeySpecParser.getOutputText(keySpec) + ? KeySpecParser.getOutputText(keySpec, code) : StringUtils.newSingleCodePointString(code); } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt index 523575461..e5ce2773c 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt @@ -92,7 +92,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? { return null return false } catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) } - if (layoutContent.startsWith("[")) { + if (layoutContent.trimStart().startsWith("[")) { // layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error try { RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } } @@ -101,7 +101,7 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? { return null } -private fun checkKeys(keys: List>): Boolean { +fun checkKeys(keys: List>): Boolean { if (keys.isEmpty() || keys.any { it.isEmpty() }) { Log.w(TAG, "empty rows") return false diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 4929237a2..197d127c5 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -8,6 +8,7 @@ import helium314.keyboard.keyboard.Key.KeyParams import helium314.keyboard.keyboard.Keyboard import helium314.keyboard.keyboard.KeyboardId import helium314.keyboard.keyboard.KeyboardLayoutSet +import helium314.keyboard.keyboard.internal.KeySpecParser import helium314.keyboard.keyboard.internal.KeyboardBuilder import helium314.keyboard.keyboard.internal.KeyboardParams import helium314.keyboard.keyboard.internal.TouchPositionCorrection @@ -20,7 +21,10 @@ import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodSubtype import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT +import helium314.keyboard.latin.utils.checkKeys import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -39,24 +43,22 @@ import java.util.Locale ]) class ParserTest { private lateinit var latinIME: LatinIME + private lateinit var params: KeyboardParams @Before 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) } - // todo: add more tests - // (popup) keys with label and code - // (popup) keys with icon - // (popup) keys with that are essentially toolbar keys (yes, this should work at some point!) - // correct background type, depending on key type and maybe sth else + // todo: add tests for background type, also consider e.g. emoji key has functional bg by default @Test fun simpleParser() { - val params = KeyboardParams() - params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) - addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL) val layoutStrings = listOf( """ a @@ -122,51 +124,109 @@ f""", // no newline at the end } } - @Test fun jsonParser() { - val params = KeyboardParams() - params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) - params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT) - addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL) - data class Expected(val label: String?, val icon: String?, val text: String?, val code: Int, val popups: List>? = null) - val expected = listOf( - Expected("a", null, null, 'a'.code, null), - Expected("a", null, null, 'a'.code, null), - Expected("a", null, null, 'b'.code, listOf("b" to 'a'.code)), - Expected("$", null, null, '$'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }), - Expected("$", null, null, '¥'.code, listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code }), - Expected("i", null, null, 105, null), - Expected("্র", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null), - Expected("x", null, "্র", KeyCode.MULTIPLE_CODE_POINTS, null), - Expected(";", null, null, ';'.code, listOf(":").map { it to it.first().code }), - Expected(".", null, null, '.'.code, listOf(">").map { it to it.first().code }), - Expected("'", null, null, '\''.code, listOf("!", "\"").map { it to it.first().code }), - Expected("9", null, null, '9'.code, null), // todo (later): also should have different background or whatever is related to type - Expected(null, "delete_key", null, -7, null), - Expected("?123", null, "?123", -202, null), - Expected(null, "space_key", null, ' '.code, null), - Expected("(", null, null, '('.code, listOf("<", "[", "{").map { it to it.first().code }), - Expected("$", null, null, '$'.code, listOf("£" to '£'.code, "₱" to '₱'.code, "€" to '€'.code, "¢" to '¢'.code, "¥" to '¥'.code, "¥" to '€'.code)), - Expected("a", null, null, ' '.code, null), - Expected("a", null, null, ' '.code, null), - Expected(null, "clipboard_action_key", null, KeyCode.CLIPBOARD, null), - Expected(null, "clipboard_action_key", null, KeyCode.MULTIPLE_CODE_POINTS, null), // todo: this works here, but crashes on phone - Expected("p", null, null, 'p'.code, listOf("$" to '$'.code)), + @Test fun simpleKey() { + assertIsExpected("""[[{ "$": "auto_text_key" "label": "a" }]]""", Expected('a'.code, "a")) + assertIsExpected("""[[{ "$": "text_key" "label": "a" }]]""", Expected('a'.code, "a")) + assertIsExpected("""[[{ "label": "a" }]]""", Expected('a'.code, "a")) + } + + @Test fun labelAndExplicitCode() { + assertIsExpected("""[[{ "$": "text_key" "label": "a", "code": 98 }]]""", Expected('b'.code, "a")) + } + + @Test fun labelAndImplicitCode() { + assertIsExpected("""[[{ "$": "text_key" "label": "a|b" }]]""", Expected('b'.code, "a")) + } + + @Test fun labelAndImplicitText() { + assertIsExpected("""[[{ "$": "text_key" "label": "a|bb" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "a", text = "bb")) + // todo: should this actually work? + assertIsExpected("""[[{ "$": "text_key" "label": "a|" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "a", text = "")) + } + + @Test fun labelAndImplicitAndExplicitCode() { // explicit code overrides implicit code + assertIsExpected("""[[{ "code": 32, "label": "a|b" }]]""", Expected(' '.code, "a")) + assertIsExpected("""[[{ "code": 32, "label": "a|!code/key_delete" }]]""", Expected(' '.code, "a")) + // todo: should text be null? it's not used at all (it could be, but it really should not) + assertIsExpected("""[[{ "code": 32, "label": "a|bb" }]]""", Expected(' '.code, "a", text = "bb")) + } + + @Test fun keyWithIconAndExplicitCode() { + assertIsExpected("""[[{ "label": "!icon/clipboard", "code": 55 }]]""", Expected(55, icon = "clipboard")) + } + + @Test fun keyWithIconAndImplicitCode() { + assertIsExpected("""[[{ "label": "!icon/clipboard_action_key|!code/key_clipboard" }]]""", Expected(KeyCode.CLIPBOARD, icon = "clipboard_action_key")) + } + + @Test fun popupKeyWithIconAndExplicitCode() { + assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key", "code": 32 } + ] + } }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code))) + } + + @Test fun popupKeyWithIconAndExplicitAndImplicitCode() { + assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|", "code": 32 } + ] + } }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code))) + assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|abc", "code": 32 } + ] + } }]]""", Expected('a'.code, "a", popups = listOf(null to ' '.code))) + } + + @Test fun labelAndImplicitCodeForPopup() { + assertIsExpected("""[[{ "$": "text_key" "label": "a|b", "popup": { "main": { "label": "b|a" } } }]]""", Expected('b'.code, "a", popups = listOf("b" to 'a'.code))) + assertIsExpected("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|" } + ] + } }]]""", Expected('a'.code, "a", + popups = listOf(null to KeyCode.MULTIPLE_CODE_POINTS)) ) - val layoutString = """ -[ - [ - { "$": "auto_text_key" "label": "a" }, - { "$": "text_key" "label": "a" }, - { "$": "text_key" "label": "a|b", "popup": { "main": { "label": "b|a" } } }, - { "label": "$$$" }, - { "label": "$$$", code: -805 }, - { "$": "case_selector", + } + + @Test fun `| works`() { + assertIsExpected("""[[{ "label": "|", "popup": { "main": { "label": "|" } } }]]""", Expected('|'.code, "|", popups = listOf("|" to '|'.code))) + } + + @Test fun currencyKey() { + assertIsExpected("""[[{ "label": "$$$" }]]""", Expected('$'.code, "$", popups = listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code })) + } + + @Test fun currencyKeyWithOtherCurrencyCode() { + assertIsExpected("""[[{ "label": "$$$", code: -805 }]]""", Expected('¥'.code, "$", popups = listOf("£", "€", "¢", "¥", "₱").map { it to it.first().code })) + } + + @Test fun currencyPopup() { + assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "$$$" } } }]]""", Expected('p'.code, "p", null, null, listOf("$" to '$'.code))) + assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "a", "code": -804 } } }]]""", Expected('p'.code, "p", null, null, listOf("a" to '€'.code))) + assertIsExpected("""[[{ "label": "p", "popup": { "main": { "label": "!icon/clipboard_action_key", "code": -804 } } }]]""", Expected('p'.code, "p", null, null, listOf(null to '€'.code))) + } + + @Test fun weirdCurrencyKey() { + assertIsExpected("""[[{ "code": -801, "label": "currency_slot_1", "popup": { + "main": { "code": -802, "label": "currency_slot_2" }, + "relevant": [ + { "code": -806, "label": "currency_slot_6" }, + { "code": -803, "label": "currency_slot_3" }, + { "code": -804, "label": "currency_slot_4" }, + { "code": -805, "label": "currency_slot_5" }, + { "code": -804, "label": "$$$4" } + ] + } }]]""", Expected('$'.code, "$", popups = listOf("£" to '£'.code, "₱" to '₱'.code, "€" to '€'.code, "¢" to '¢'.code, "¥" to '¥'.code, "¥" to '€'.code))) + } + + @Test fun caseSelector() { + assertIsExpected("""[[{ "$": "case_selector", "lower": { "code": 105, "label": "i" }, "upper": { "code": 304, "label": "İ" } - }, - { "$": "multi_text_key", "codePoints": [2509, 2480], "label": "্র" }, - { "$": "multi_text_key", "codePoints": [2509, 2480], "label": "x" }, - { "$": "case_selector", + }]]""", Expected(105, "i")) + } + + @Test fun caseSelectorWithPopup() { + assertIsExpected("""[[{ "$": "case_selector", "lower": { "code": 59, "label": ";", "popup": { "relevant": [ { "code": 58, "label": ":" } @@ -177,8 +237,11 @@ f""", // no newline at the end { "code": 59, "label": ";" } ] } } - }, - { "$": "shift_state_selector", + }]]""", Expected(';'.code, ";", popups = listOf(":").map { it to it.first().code })) + } + + @Test fun shiftSelector() { + assertIsExpected("""[[{ "$": "shift_state_selector", "shiftedManual": { "code": 62, "label": ">", "popup": { "relevant": [ { "code": 46, "label": "." } @@ -189,8 +252,11 @@ f""", // no newline at the end { "code": 62, "label": ">" } ] } } - }, - { "$": "shift_state_selector", + }]]""", Expected('.'.code, ".", popups = listOf(">").map { it to it.first().code })) + } + + @Test fun nestedSelectors() { + assertIsExpected("""[[{ "$": "shift_state_selector", "shiftedManual": { "code": 34, "label": "\"", "popup": { "relevant": [ { "code": 33, "label": "!" }, @@ -207,12 +273,11 @@ f""", // no newline at the end ] } } } - }, - { "code": 57, "label": "9", "type": "numeric" }, - { "code": -7, "label": "delete", "type": "enter_editing" }, - { "code": -207, "label": "view_phone2", "type": "system_gui" }, - { "code": 32, "label": "space" }, - { "$": "layout_direction_selector", + }]]""", Expected('\''.code, "'", popups = listOf("!", "\"").map { it to it.first().code })) + } + + @Test fun layoutDirectionSelector() { + assertIsExpected("""[[{ "$": "layout_direction_selector", "ltr": { "code": 40, "label": "(", "popup": { "main": { "code": 60, "label": "<" }, "relevant": [ @@ -227,70 +292,129 @@ f""", // no newline at the end { "code": 125, "label": "{" } ] } } - }, - { "code": -801, "label": "currency_slot_1", "popup": { - "main": { "code": -802, "label": "currency_slot_2" }, - "relevant": [ - { "code": -806, "label": "currency_slot_6" }, - { "code": -803, "label": "currency_slot_3" }, - { "code": -804, "label": "currency_slot_4" }, - { "code": -805, "label": "currency_slot_5" }, - { "code": -804, "label": "$$$4" } - ] - } }, - { "code": 32, "label": "a|!code/key_delete" }, - { "code": 32, "label": "a|b" }, - { "label": "!icon/clipboard_action_key|!code/key_clipboard" }, - { "label": "!icon/clipboard_action_key" }, - { "label": "p", "popup": { "main": { "label": "$$$" } } } - ], - [ - { "label": "q" }, - { "label": "s" }, - { "label": "d" }, - { "label": "f" }, - { "label": "g" }, - { "label": "h" }, - { "label": "j" }, - { "label": "k" }, - { "label": "l" }, - { "label": "m", "popup": { "main": { "label": "/" } } } - ], - [ - { "label": "w", "popup": { + }]]""", Expected('('.code, "(", popups = listOf("<", "[", "{").map { it to it.first().code })) + } + + @Test fun autoMultiTextKey() { + assertIsExpected("""[[{ "label": "্র" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "্র", text = "্র")) + } + + @Test fun multiTextKey() { // pointless without codepoints! + assertIsExpected("""[[{ "$": "multi_text_key", "codePoints": [2509, 2480], "label": "্র" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "্র", text = "্র")) + assertIsExpected("""[[{ "$": "multi_text_key", "codePoints": [2509, 2480], "label": "x" }]]""", Expected(KeyCode.MULTIPLE_CODE_POINTS, "x", text = "্র")) + } + + @Test fun negativeCode() { + assertIsExpected("""[[{ "code": -7, "label": "delete" }]]""", Expected(-7, icon = "delete_key")) + } + + @Test fun keyWithType() { + assertIsExpected("""[[{ "code": 57, "label": "9", "type": "numeric" }]]""", Expected(57, "9")) + assertIsExpected("""[[{ "code": -7, "label": "delete", "type": "enter_editing" }]]""", Expected(-7, icon = "delete_key")) + // -207 gets translated to -202 in Int.toKeyEventCode + assertIsExpected("""[[{ "code": -207, "label": "view_phone2", "type": "system_gui" }]]""", Expected(-202, "?123", text = "?123")) + } + + @Test fun spaceKey() { + assertIsExpected("""[[{ "code": 32, "label": "space" }]]""", Expected(32, icon = "space_key")) + } + + @Test fun invalidKeys() { + assertThrows(KeySpecParser.KeySpecParserError::class.java) { + RawKeyboardParser.parseJsonString("""[[{ "label": "!icon/clipboard_action_key" }]]""").map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } } + } + } + + @Test fun popupWithCodeAndLabel() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "w", "popup": { "main": { "code": 55, "label": "!" } - } }, - { "label": "x", "popup": { + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals("!", key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals('7'.code, key.toKeyParams(params).mPopupKeys?.first()?.mCode) + } + + @Test fun popupWithCodeAndIcon() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "w", "popup": { + "main": { "code": 55, "label": "!icon/clipboard_action_key" } + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("clipboard_action_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals('7'.code, key.toKeyParams(params).mPopupKeys?.first()?.mCode) + } + + @Test fun popupToolbarKey() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "x", "popup": { "main": { "label": "undo" } - } }, - { "label": "c", "popup": { - "main": { "code": -10001, "label": "x" } - } }, - { "label": "v" }, - { "label": "b" }, - { "label": "n" } - ] -] - """.trimIndent() - val keys = RawKeyboardParser.parseJsonString(layoutString).map { it.mapNotNull { it.compute(params) } } - keys.first().forEachIndexed { index, keyData -> - println("data: key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}") - val keyParams = keyData.toKeyParams(params) - println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}") - assertEquals(expected[index].label, keyParams.mLabel) - assertEquals(expected[index].icon, keyParams.mIconName) - assertEquals(expected[index].code, keyParams.mCode) - // todo (later): what's wrong with popup order? - assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first }) - assertEquals(expected[index].text, keyParams.outputText) + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("undo", key.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(KeyCode.UNDO, key.toKeyParams(params).mPopupKeys?.first()?.mCode) + } + + @Test fun popupKeyWithIconAndImplicitText() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|aa" } + ] + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("go_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(KeyCode.MULTIPLE_CODE_POINTS, key.toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals("aa", key.toKeyParams(params).mPopupKeys?.first()?.mOutputText) + + val key2 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|" } + ] + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("go_key", key2.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(KeyCode.MULTIPLE_CODE_POINTS, key2.toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals("", key2.toKeyParams(params).mPopupKeys?.first()?.mOutputText) + } + + // output text is null here, maybe should be changed? + @Test fun popupKeyWithIconAndCodeAndImplicitText() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|", "code": 55 } + ] + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("go_key", key.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(55, key.toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals(null, key.toKeyParams(params).mPopupKeys?.first()?.mOutputText) + + val key2 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|a", "code": 55 } + ] + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("go_key", key2.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(55, key2.toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals(null, key2.toKeyParams(params).mPopupKeys?.first()?.mOutputText) + + val key3 = RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { "relevant": [ + { "label": "!icon/go_key|aa", "code": 55 } + ] + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals(null, key3.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals("go_key", key3.toKeyParams(params).mPopupKeys?.first()?.mIconName) + assertEquals(55, key3.toKeyParams(params).mPopupKeys?.first()?.mCode) + assertEquals(null, key3.toKeyParams(params).mPopupKeys?.first()?.mOutputText) + } + + @Test fun invalidPopupKeys() { + assertThrows(KeySpecParser.KeySpecParserError::class.java) { + RawKeyboardParser.parseJsonString("""[[{ "label": "a", "popup": { + "main": { "label": "!icon/clipboard_action_key" } + } }]]""").map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } } } - assertEquals("!", keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mLabel) - assertEquals('7'.code, keys.last()[0].toKeyParams(params).mPopupKeys?.first()?.mCode) - assertEquals(null, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mLabel) - assertEquals("undo", keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mIconName) - assertEquals(KeyCode.UNDO, keys.last()[1].toKeyParams(params).mPopupKeys?.first()?.mCode) - assertEquals("x", keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mLabel) - assertEquals(-10001, keys.last()[2].toKeyParams(params).mPopupKeys?.first()?.mCode) + } + + @Test fun popupSymbolAlpha() { + val key = RawKeyboardParser.parseJsonString("""[[{ "label": "c", "popup": { + "main": { "code": -10001, "label": "x" } + } }]]""").map { it.mapNotNull { it.compute(params) } }.flatten().single() + assertEquals("x", key.toKeyParams(params).mPopupKeys?.first()?.mLabel) + assertEquals(-10001, key.toKeyParams(params).mPopupKeys?.first()?.mCode) } @Test fun canLoadKeyboard() { @@ -334,6 +458,28 @@ f""", // no newline at the end } } + private data class Expected(val code: Int, val label: String? = null, val icon: String? = null, val text: String? = null, val popups: List>? = null) + + private fun assertIsExpected(json: String, expected: Expected) { + assertAreExpected(json, listOf(expected)) + } + + private fun assertAreExpected(json: String, expected: List) { + val keys = RawKeyboardParser.parseJsonString(json).map { it.mapNotNull { it.compute(params) } }.flatten() + keys.forEachIndexed { index, keyData -> + println("data: key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}") + val keyParams = keyData.toKeyParams(params) + println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}") + assertEquals(expected[index].label, keyParams.mLabel) + assertEquals(expected[index].icon, keyParams.mIconName) + assertEquals(expected[index].code, keyParams.mCode) + // todo (later): what's wrong with popup order? + assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first }) + assertEquals(expected[index].text, keyParams.outputText) + assertTrue(checkKeys(listOf(listOf(keyParams)))) + } + } + private fun buildKeyboard(editorInfo: EditorInfo, subtype: InputMethodSubtype, elementId: Int): Pair>> { val layoutParams = KeyboardLayoutSet.Params() val editorInfoField = KeyboardLayoutSet.Params::class.java.getDeclaredField("mEditorInfo").apply { isAccessible = true } diff --git a/layouts.md b/layouts.md index 958942cdf..a4824c6b9 100644 --- a/layouts.md +++ b/layouts.md @@ -98,8 +98,6 @@ Usually the label is what is displayed on the key. However, there are some speci * If you want different key label and input text, set the label to [label]|[text], e.g. `aa|bb` will show `aa`, but pressing the key will input `bb`. You can also specify special key codes like `a|!code/key_action_previous`, but it's cleaner to use a json layout and specify the code explicitly. Note that when specifying a code in the label, and a code in a json layout, the code in the label will be ignored. * It's also possible to specify an icon, like `!icon/previous_key|!code/key_action_previous`. - * For normal keys, even if you specify a code, you will need to add a `|` to the label, e.g. `!icon/go_key|` or `!icon/go_key|ignored` (to be fixed). - * For popups keys, you must _not_ add a `|` (to be fixed). * You can find available icon names in [KeyboardIconsSet](/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt). You can also use toolbar key icons using the uppercase name of the [toolbar key](/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt#L109), e.g. `!icon/redo` ## Adding new layouts / languages From 08f194a369aec18b4bcd6e9be3aa02bb58e44a12 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 21 Jun 2024 15:13:13 +0200 Subject: [PATCH 47/92] block key swiping while using sliding key input fixes #892, which also contains the fix by @devycarol --- .../java/helium314/keyboard/keyboard/PointerTracker.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java index b08220422..6feb056a3 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java @@ -870,7 +870,8 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime) final Key oldKey = mCurrentKey; final SettingsValues sv = Settings.getInstance().getCurrent(); - if (oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) { + // todo (later): move key swipe stuff to a separate function (and finally extend it) + if (!mIsInSlidingKeyInput && oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) { // reason for timeout: https://github.com/openboard-team/openboard/issues/411 final int longpressTimeout = 2 * sv.mKeyLongpressTimeout / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; if (mStartTime + longpressTimeout > System.currentTimeMillis()) @@ -899,7 +900,7 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime) return; } - if (oldKey != null && oldKey.getCode() == KeyCode.DELETE && sv.mDeleteSwipeEnabled) { + if (!mIsInSlidingKeyInput && oldKey != null && oldKey.getCode() == KeyCode.DELETE && sv.mDeleteSwipeEnabled) { // Delete slider int steps = (x - mStartX) / sPointerStep; if (abs(steps) > 2 || (mInHorizontalSwipe && steps != 0)) { From 4a9dc6bff619763559b0f604e5fa337f82863b1c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 23 Jun 2024 16:12:40 +0200 Subject: [PATCH 48/92] reload theme on orientation change should actually happen anyway, but possibly not for newer android versions? could help with #401 --- .../helium314/keyboard/keyboard/KeyboardSwitcher.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index 6fd68514b..aa334334a 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -71,6 +71,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private KeyboardTheme mKeyboardTheme; private Context mThemeContext; private int mCurrentUiMode; + private int mCurrentOrientation; @SuppressLint("StaticFieldLeak") // this is a keyboard, we want to keep it alive in background private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); @@ -111,14 +112,16 @@ public void forceUpdateKeyboardTheme(@NonNull Context displayContext) { private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context, final KeyboardTheme keyboardTheme) { - final boolean nightModeChanged = (mCurrentUiMode & Configuration.UI_MODE_NIGHT_MASK) - != (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK); - if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme) || nightModeChanged + if (mThemeContext == null + || !keyboardTheme.equals(mKeyboardTheme) + || mCurrentOrientation != context.getResources().getConfiguration().orientation + || (mCurrentUiMode & Configuration.UI_MODE_NIGHT_MASK) != (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) || !mThemeContext.getResources().equals(context.getResources()) || Settings.getInstance().getCurrent().mColors.haveColorsChanged(context)) { mKeyboardTheme = keyboardTheme; mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); mCurrentUiMode = context.getResources().getConfiguration().uiMode; + mCurrentOrientation = context.getResources().getConfiguration().orientation; KeyboardLayoutSet.onKeyboardThemeChanged(); return true; } From fc0d27459ff0d9d479b8ea731c47c0c14903defb Mon Sep 17 00:00:00 2001 From: codokie <151087174+codokie@users.noreply.github.com> Date: Mon, 24 Jun 2024 19:32:16 +0300 Subject: [PATCH 49/92] fix phantom space after double quotes (#842) and add a bunch of related tests --------- Co-authored-by: Helium314 --- .../keyboard/latin/inputlogic/InputLogic.java | 16 +++---- .../keyboard/latin/InputLogicTest.kt | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 272424b66..9ed916614 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -1112,32 +1112,32 @@ private void handleSeparatorEvent(final Event event, final InputTransaction inpu mConnection.commitCodePoint(codePoint); } } else { - if ((SpaceState.PHANTOM == inputTransaction.getMSpaceState() - && settingsValues.isUsuallyFollowedBySpace(codePoint)) - || (Constants.CODE_DOUBLE_QUOTE == codePoint - && isInsideDoubleQuoteOrAfterDigit)) { + if (SpaceState.PHANTOM == inputTransaction.getMSpaceState() + && (settingsValues.isUsuallyFollowedBySpace(codePoint) || isInsideDoubleQuoteOrAfterDigit)) { // If we are in phantom space state, and the user presses a separator, we want to // stay in phantom space state so that the next keypress has a chance to add the // space. For example, if I type "Good dat", pick "day" from the suggestion strip // then insert a comma and go on to typing the next word, I want the space to be // inserted automatically before the next word, the same way it is when I don't - // input the comma. A double quote behaves like it's usually followed by space if - // we're inside a double quote. + // input the comma. Also when closing a quote the phantom state should be preserved. // The case is a little different if the separator is a space stripper. Such a // separator does not normally need a space on the right (that's the difference // between swappers and strippers), so we should not stay in phantom space state if // the separator is a stripper. Hence the additional test above. mSpaceState = SpaceState.PHANTOM; - } else + } else { // mSpaceState is still SpaceState.NONE, but some characters should typically // be followed by space. Set phantom space state for such characters if the user // enabled the setting and was not composing a word. The latter avoids setting // phantom space state when typing decimal numbers, with the drawback of not // setting phantom space state after ending a sentence with a non-word. + // A double quote behaves like it's usually followed by space if we're inside + // a double quote. if (wasComposingWord && settingsValues.mAutospaceAfterPunctuationEnabled - && settingsValues.isUsuallyFollowedBySpace(codePoint)) { + && (settingsValues.isUsuallyFollowedBySpace(codePoint) || isInsideDoubleQuoteOrAfterDigit)) { mSpaceState = SpaceState.PHANTOM; + } } mConnection.commitCodePoint(codePoint); diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt index 3d438db87..98dcbb83f 100644 --- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt +++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt @@ -545,8 +545,50 @@ class InputLogicTest { assertEquals("hello ", text) } + @Test fun `no weird space inside multi-"`() { + reset() + chainInput("\"\"\"") + assertEquals("\"\"\"", text) + + reset() + DeviceProtectedUtils.getSharedPreferences(latinIME).edit { putBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, true) } + chainInput("\"\"\"") + assertEquals("\"\"\"", text) + } + + @Test fun `autospace still happens after "`() { + reset() + DeviceProtectedUtils.getSharedPreferences(latinIME).edit { putBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, true) } + chainInput("\"hello\"you") + assertEquals("\"hello\" you", text) + } + + @Test fun `autospace still happens after " if next word is in quotes`() { + reset() + DeviceProtectedUtils.getSharedPreferences(latinIME).edit { putBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, true) } + chainInput("\"hello\"\"you\"") + assertEquals("\"hello\" \"you\"", text) + } + + @Test fun `autospace propagates over "`() { + reset() + input('"') + pickSuggestion("hello") + assertEquals(spaceState, SpaceState.PHANTOM) // picking a suggestion sets phantom space state + chainInput("\"you") + assertEquals("\"hello\" you", text) + } + + @Test fun `autospace still happens after " if nex word is in " and after comma`() { + reset() + DeviceProtectedUtils.getSharedPreferences(latinIME).edit { putBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, true) } + chainInput("\"hello\",\"you\"") + assertEquals("\"hello\", \"you\"", text) + } + @Test fun `autospace in json editor`() { reset() + DeviceProtectedUtils.getSharedPreferences(latinIME).edit { putBoolean(Settings.PREF_AUTOSPACE_AFTER_PUNCTUATION, true) } chainInput("{\"label\":\"") assertEquals("{\"label\": \"", text) input('c') From 2c3493fe933210acb1313149e1289ffdf7da269b Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 18:53:27 +0200 Subject: [PATCH 50/92] update tests remove failing test because issue is not reproducible on device any more add special build variant intended for running tests in github workflow --- app/build.gradle | 4 ++++ app/src/test/java/helium314/keyboard/Shadows.kt | 5 ++++- .../java/helium314/keyboard/latin/InputLogicTest.kt | 13 +------------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 43a6986c0..ad5dbf11d 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,6 +38,10 @@ android { jniDebuggable false applicationIdSuffix ".debug" } + runTests { // build variant for running tests on CI that skips tests known to fail + minifyEnabled true + jniDebuggable false + } archivesBaseName = "HeliBoard_" + defaultConfig.versionName } diff --git a/app/src/test/java/helium314/keyboard/Shadows.kt b/app/src/test/java/helium314/keyboard/Shadows.kt index 3a4d70546..6d5295fdb 100644 --- a/app/src/test/java/helium314/keyboard/Shadows.kt +++ b/app/src/test/java/helium314/keyboard/Shadows.kt @@ -9,6 +9,7 @@ import android.view.inputmethod.InputMethodSubtype import androidx.core.app.LocaleManagerCompat import androidx.core.os.LocaleListCompat import com.android.inputmethod.latin.utils.BinaryDictionaryUtils +import helium314.keyboard.latin.BuildConfig import helium314.keyboard.latin.common.StringUtils import org.robolectric.annotation.Implementation import org.robolectric.annotation.Implements @@ -27,7 +28,9 @@ object ShadowLocaleManagerCompat { class ShadowInputMethodManager2 : ShadowInputMethodManager() { @Implementation override fun getInputMethodList() = listOf( - InputMethodInfo("helium314.keyboard.debug", "LatinIME", "HeliBoard debug", null), + if (BuildConfig.BUILD_TYPE == "debug") + InputMethodInfo("helium314.keyboard.debug", "LatinIME", "HeliBoard debug", null) + else InputMethodInfo("helium314.keyboard", "LatinIME", "HeliBoard", null), ) @Implementation fun getShortcutInputMethodsAndSubtypes() = emptyMap>() diff --git a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt index 98dcbb83f..2d3199f30 100644 --- a/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt +++ b/app/src/test/java/helium314/keyboard/latin/InputLogicTest.kt @@ -137,6 +137,7 @@ class InputLogicTest { // todo: make it work, but it might not be that simple because adding is done in combiner // https://github.com/Helium314/HeliBoard/issues/214 @Test fun insertLetterIntoWordHangul() { + if (BuildConfig.BUILD_TYPE == "runTests") return reset() currentScript = ScriptUtils.SCRIPT_HANGUL chainInput("ㅛㅎㄹㅎㅕㅛ") @@ -595,18 +596,6 @@ class InputLogicTest { assertEquals("{\"label\": \"c", text) } - // todo: the test fails because assert wants it as it's in app - // but actually the "failing text" is the wanted behavior -> how to get it in app? - @Test fun `autospace in json editor 2`() { - reset() - setInputType(InputType.TYPE_CLASS_TEXT and InputType.TYPE_TEXT_FLAG_MULTI_LINE) - setText("[\n[\n{ \"label\": \"a\" },\n") - chainInput("{\"label\":\"") - assertEquals("[\n[\n{ \"label\": \"a\" },\n{\"label\":\"", text) - input('c') - assertEquals("[\n[\n{ \"label\": \"a\" },\n{\"label\":\" c", text) - } - // ------- helper functions --------- // should be called before every test, so the same state is guaranteed From e707ade006bf7c86b1cedcb31e54e32447faf3d0 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 19:28:30 +0200 Subject: [PATCH 51/92] new version and updated translations --- app/build.gradle | 4 +- app/src/main/res/values-bn/strings.xml | 89 +++++++++++++++---- app/src/main/res/values-es-rUS/strings.xml | 11 +++ app/src/main/res/values-it/strings.xml | 13 +-- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 28 +++++- .../metadata/android/ar/changelogs/2002.txt | 4 + .../android/en-US/changelogs/2003.txt | 11 +++ .../android/nl-NL/changelogs/2002.txt | 4 + .../android/nl-NL/full_description.txt | 2 +- 10 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 fastlane/metadata/android/ar/changelogs/2002.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/2003.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/2002.txt diff --git a/app/build.gradle b/app/build.gradle index ad5dbf11d..15cb5648b 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "helium314.keyboard" minSdkVersion 21 targetSdkVersion 34 - versionCode 2002 - versionName '2.0-beta2' + versionCode 2003 + versionName '2.0' ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a74fe5a2a..ff80f4329 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -88,7 +88,7 @@ সর্বদা নম্বর সারি প্রদর্শন বোতামের পরামর্শ প্রদর্শন দীর্ঘ চাপের পরামর্শ প্রদর্শন - ফাংশন ইঙ্গিত প্রদর্শন + ফাংশনাল ইঙ্গিত প্রদর্শন কোনো বোতামে দীর্ঘ চাপে অতিরিক্ত ফাংশন থাকলে তার ইঙ্গিত দেখাবে স্পেস বোতামে ইনপুট পদ্ধতি পরিবর্তন স্পেস বোতামে দীর্ঘ চাপ দিয়ে ইনপুট পদ্ধতি নির্বাচনের মেনু আনয়ন @@ -100,7 +100,37 @@ প্রচ্ছন্ন বৈশিষ্ট্য অপরিদৃষ্ট হতে পারে এমন বৈশিষ্ট্যের বর্ণনা ডিভাইস সুরক্ষিত স্টোরেজ - ► ক্লিপবোর্ড বোতামে (পরামর্শ ফলকের ঐচ্ছিক বোতাম) দীর্ঘ চাপ দিলে সিস্টেম ক্লিপবোর্ডের আধেয় পেস্ট করবে। <br> <br> ► পরামর্শ ফলকের টুলবারের কোনো বোতামে দীর্ঘ চাপ দিলে তা পরামর্শ ফলকে আবদ্ধ হবে। <br> <br> ► কমা বোতামে দীর্ঘ চাপ দিলে ক্লিপবোর্ড ভিউ, ইমোজি ভিউ, একক হাত মোড, সেটিংস অথবা ভাষা পরিবর্তন বোতাম উপলব্ধ হবে: <br> \t• সংশ্লিষ্ট বোতাম সক্ষম থাকলে ইমোজি ভিউ বা ভাষা পরিবর্তন বোতাম দৃশ্যমান থাকবে না; <br> \t• কিছু লেআউটের জন্য কমা বোতাম নয়, কিন্তু একই জায়গার বোতামটি, যেমন: Dvorak লেআউটে \'q\' বোতাম। <br> <br> ► ছদ্মবেশী বোতামে চাপ দিলে টুলবার উপলব্ধ হবে। <br> <br> ► স্লাইডিং কি ইনপুট: বড়ো হাতের অক্ষর লেখার জন্য শিফট বোতাম থেকে অন্য বোতামে সোয়াইপ করুন: <br> \t• সিম্বল কিবোর্ড থেকে সিম্বল এবং সম্পর্কিত অক্ষর লেখার জন্য \'?123\' বোতামেও এটি কাজ করে।<br> <br> ► পরামর্শ ফলকে কোনো পরামর্শে দীর্ঘ চাপ দিলে আরও পরামর্শ, অপসারণ করার জন্য অপসারণ বোতাম প্রদর্শিত হবে। <br> <br> ► আরও পরামর্শ খুলতে কোনো পরামর্শ থেকে উপরে সোয়াইপ করুন এবং নির্দিষ্ট পরামর্শ নির্বাচনের জন্য ছেড়ে দিন। <br> <br> ► আপনি ফাইল এক্সপ্লোরারে খোলার মাধ্যমে অভিধান সংযুক্ত করতে পারেন: <br> \t• এটি কেবল <i>content-uris</i> এর সাথে কাজ করে, <i>file-uris</i> এর সাথে নয়। অর্থাৎ কিছু ফাইল এক্সপ্লোরারের সাথে নাও কাজ করতে পারে। <br> <br> <i>কেবল ডিবাগ এপিকে</i> <br> ► পরামর্শে দীর্ঘ চাপ দিলে তার উৎস-অভিধান দেখাবে। <br> <br> ► ডিবাগ সেটিংস উচ্চতর সেটিংসে উপলব্ধ। যদিও লগ ফাইলে অভিধান অবমুক্ত করা ছাড়া এর ব্যবহারযোগ্যতা সীমিত। <br> <br> ► অ্যাপ্লিকেশন ক্র্যাশের পরে সেটিংস খুললে আপনি ক্র্যাশ লগ চান কি না তা জিজ্ঞেস করা হবে৷ <br> <br> ► রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: Android 7 থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। <br> ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। <br> ফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে। + ► পিন করে রাখা টুলবার বোতামে দীর্ঘ চাপ দিলে তা অতিরিক্ত কাজ করবে: <br> +\n\t• ক্লিপবোর্ড &#65515; পেস্ট <br> +\n\t• বামে/ডানে স্থানান্তর &#65515; সম্পূর্ণ বামে/ডানে স্থানান্তর <br> +\n\t• উপরে/নিচে স্থানান্তর &#65515; পৃষ্ঠার উপরে/নিচে <br> +\n\t• অনুলিপি &#65515; সমস্ত অনুলিপি করবে <br> +\n\t• শব্দ নির্বাচন &#65515; সমস্ত নির্বাচন <br> +\n\t• পূর্বাবস্থা &#8596; পুনরাবৃত্তি <br> <br> +\n► পরামর্শ ফলকের টুলবারের কোনো বোতামে দীর্ঘ চাপ দিলে তা পরামর্শ ফলকে আবদ্ধ হবে। <br> <br> +\n► কমা বোতামে দীর্ঘ চাপ দিলে ক্লিপবোর্ড ভিউ, ইমোজি ভিউ, একক হাত মোড, সেটিংস অথবা ভাষা পরিবর্তন বোতাম উপলব্ধ হবে:<br> +\n\t• সংশ্লিষ্ট বোতাম সক্ষম থাকলে ইমোজি ভিউ বা ভাষা পরিবর্তন বোতাম দৃশ্যমান থাকবে না; <br> +\n\t• কিছু লেআউটের জন্য কমা বোতাম নয়, কিন্তু একই জায়গার বোতামটি, যেমন: Dvorak লেআউটে \'q\' বোতাম। <br> <br> +\n► ছদ্মবেশী বোতামে চাপ দিলে টুলবার উপলব্ধ হবে। <br> <br> +\n► স্লাইডিং কি ইনপুট: বড়ো হাতের অক্ষর লেখার জন্য শিফট বোতাম থেকে অন্য বোতামে সোয়াইপ করুন: <br> +\n\t• সিম্বল কিবোর্ড থেকে সিম্বল এবং সম্পর্কিত অক্ষর লেখার জন্য \'?123\' বোতামেও এটি কাজ করে।<br> <br> +\n► শিফট অথবা প্রতীক বোতাম চেপে ধরে এক বা একাধিক বোতাম চাপার পর শিফট অথবা প্রতীক বোতাম ছেড়ে দিলে তা পূর্ববর্তী কিবোর্ডে নিয়ে যাবে। <br> <br> +\n► পরামর্শ ফলকে কোনো পরামর্শে দীর্ঘ চাপ দিলে আরও পরামর্শ, অপসারণ করার জন্য অপসারণ বোতাম প্রদর্শিত হবে। <br> <br> +\n► আরও পরামর্শ খুলতে কোনো পরামর্শ থেকে উপরে সোয়াইপ করুন এবং নির্দিষ্ট পরামর্শ নির্বাচনের জন্য ছেড়ে দিন। <br> <br> +\n► ক্লিপবোর্ড ভিউয়ে বাম পাশে সোয়াইপ করলে আইটেম অপসারণ হবে (যদি না সেটি পিন করা থাকে)। <br> <br> +\n► পাঠ্য সিলেক্ট করে শিফট চাপলে তা বড়ো হাতের অক্ষর, ছোটো হাতের অক্ষর এবং সব অক্ষর বড়ো হাতের হবে। <br> <br> +\n► আপনি ফাইল এক্সপ্লোরারে খোলার মাধ্যমে অভিধান সংযুক্ত করতে পারেন: <br> +\n\t• এটি কেবল <i>content-uris</i> এর সাথে কাজ করে, <i>file-uris</i> এর সাথে নয়। অর্থাৎ কিছু ফাইল এক্সপ্লোরারের সাথে নাও কাজ করতে পারে। <br> <br> +\n► রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: <br> +\nঅ্যান্ড্রয়েড ৭ থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। <br> +\nফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে। <br> <br> +\n<i><b>কেবল ডিবাগ এপিকে</b></i> <br> <br> +\n► পরামর্শে দীর্ঘ চাপ দিলে তার উৎস-অভিধান দেখাবে। <br> <br> +\n► ডিবাগ সেটিংস উচ্চতর সেটিংসে উপলব্ধ। যদিও লগ ফাইলে অভিধান অবমুক্ত করা ছাড়া এর ব্যবহারযোগ্যতা সীমিত। <br> +\n\t• রিলিজ এপিকে এর জন্য <i>সম্পর্কে</i> এর সংস্করণে কয়েকবার চাপ দিতে হবে। তাহলে <i>উচ্চতর</i>-এ ডিবাগ সেটিংস উপলব্ধ হবে। <br> +\n\t• <i>পরামর্শ তথ্য প্রদর্শন</i> চালু করলে পরামর্শের উপরে কিছু ছোটো নম্বর থাকবে যা অভ্যন্তরীণ হিসাব ও উৎস অভিধান নির্দেশ করে। <br> <br> +\n► অ্যাপ্লিকেশন ক্র্যাশের পরে সেটিংস খুললে আপনি ক্র্যাশ লগ চান কি না তা জিজ্ঞেস করা হবে৷ <br> <br> +\n► বহুভাষী টাইপিং ব্যবহারের সময় স্পেসবার বর্তমান ভাষা নির্ধারণের জন্য একটি দৃঢ়তা মান দেখাবে। সার্বীয় (ল্যাটিন) ইংরেজি (ইউকে) (%s) ইংরেজি (ইউএস) (%s) @@ -160,16 +190,18 @@ %2$s এর \"%1$s\" অভিধান কোন ভাষার জন্য সংযুক্ত হবে? ভাষা নির্বাচন %s এ সংযুক্তি - "ব্যবহারকারী-যোগকৃত অভিধান \"%s\" প্রতিস্থাপন করতে নিশ্চিত?\n -বর্তমান অভিধান: -%2$s\n -নতুন অভিধান: -%3$s" + ব্যবহারকারী-যোগকৃত অভিধান \"%1$s\" প্রতিস্থাপন করতে নিশ্চিত? +\n +\nবর্তমান অভিধান: +\n%2$s +\n +\nনতুন অভিধান: +\n%3$s "অভিধান প্রতিস্থাপন" - "ব্যবহারকারী-যোগকৃত অভিধান \"%1$s\" অপসারণ করতে নিশ্চিত?" + ব্যবহারকারী-যোগকৃত অভিধান \"%s\" অপসারণ করতে নিশ্চিত? অভিধান ব্যতীত কেবল পূর্বে প্রবেশিত শব্দের জন্য পরামর্শ পাওয়া যাবে।<br> - আপনি অভিধান ডাউনলোড করতে পারেন %1$s, অথবা \"%2$s\" এর জন্য অভিধান সরাসরি ডাউনলোড করা যায় কি না যাচাই করতে পারেন %3$s." - পুনরায় প্রদর্শন করবে না +\nআপনি অভিধান ডাউনলোড করতে পারেন %1$s, অথবা \"%2$s\" এর জন্য অভিধান সরাসরি ডাউনলোড করা যায় কি না যাচাই করতে পারেন %3$s। + পুনরায় প্রদর্শিত হবে না অভিধান যোগ করার জন্য নির্বাচন করুন। %s .dict ফরম্যাটে অভিধান ডাউনলোড করা যেতে পারে। "ত্রুটি: স্ক্রিপ্ট এই কিবোর্ডের সাথে সামঞ্জস্যপূর্ণ নয়" এখানে @@ -231,8 +263,8 @@ সেটিংস খুলতে ভাষাতে ট্যাপ করুন "কিবোর্ড পরিবর্তন" "অবয়ব" - "পূর্বাবস্থায় ফিরুন" - "আবার করুন" + পূর্বাবস্থা + পুনরাবৃত্তি "পরামর্শ উন্নত করতে আপনার যোগাযোগ এবং টাইপ করা তথ্য থেকে শিখবে" "যান" "পরবর্তী" @@ -335,10 +367,33 @@ স্পেসবারের পটভূমি স্পেসবারে উল্লম্ব অঙ্গুলিহেলন স্পেসবারে অনুভূমিক অঙ্গুলিহেলন - কাটো - নাম্বারপ্যাডের জন্য সিম্বল বোতাম কিছুক্ষণ ধরে রাখো - পরিবর্তনশীল টুলবার দিক - ডান থেকে বাম কিবোর্ড নির্বাচন করলে উল্টো দিকে যাও + কর্তন + নম্বর প্যাডের জন্য প্রতীক বোতামে দীর্ঘ চাপ + টুলবারের পরিবর্তনশীল দিক + ডান থেকে বাম কিবোর্ড নির্বাচন করলে দিক বিপরীত হবে %s (শিক্ষার্থী) - ভাষা পরিবর্তন বোতাম + ভাষা পরিবর্তন বোতামের আচরণ + পিন করে রাখা টুলবার বোতাম নির্বাচন + টুলবার বোতামে দীর্ঘ চাপ পিন করবে + আধেয় অনুলিপি করা হয়েছে + ইনপুট শুরু হলে বা পাঠ্য সিলেক্ট করা হলে টুলবার প্রদর্শিত হবে + টুলবার স্বয়ংক্রিয়ভাবে আড়াল হবে + পরামর্শ উপলব্ধ হলে টুলবার আড়াল হবে + স্বয়ংক্রিয় টুলবার প্রদর্শন + ক্লিপবোর্ড ইতিহাস বন্ধকরণ + ক্লিপবোর্ড টুলবার বোতাম নির্বাচন + %s (বর্ধিত) + ইমোজি + টুলবার + সর্বদা মধ্যবর্তী পরামর্শ ব্যবহার করবে + স্পেস বা বিরামচিহ্ন চাপ দিলে মধ্যবর্তী পরামর্শ সন্নিবেশিত হবে + মানসি + মানসি (%s) + পিন করা নেই এমন অন্য টুলবার বোতামের দীর্ঘচাপের কার্যাবলি নিষ্ক্রিয় করবে + অধিক রং প্রদর্শন + এই সেটিংস অভ্যন্তরীণভাবে ব্যবহৃত সকল রং প্রকাশ করে। রঙের তালিকা যেকোনো সময় পরিবর্তিত হতে পারে। কোনো ডিফল্ট রং নেই, এবং নাম অনুবাদ করা হবে না। + ফাংশনাল বোতাম লেআউট নিজস্বীকরণ + ফাংশনাল বোতাম + ফাংশনাল বোতাম (প্রতীক) + ফাংশনাল বোতাম (অধিক প্রতীক) \ No newline at end of file diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index a050b75e6..39bd5bef6 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -357,4 +357,15 @@ Teclas funcionales (Más símbolos) Personalizar diseño de teclas funcionales Teclas funcionales (Símbolos) + Seleccionar teclas ancladas en la barra de herramientas + Fijar la tecla en la barra de herramientas con una pulsación larga + Esto desactivará otras acciones de pulsación larga para las teclas de la barra de herramientas que no estén ancladas + Contenido copiado + Ocultar automáticamente la barra de herramientas + Mostrar automáticamente la barra de herramientas + Mostrar la barra de herramientas si se inicia la entrada o se selecciona el texto + Comportamiento de la tecla de cambio de idioma + Ocultar la barra de herramientas cuando las sugerencias estén disponibles + Emoji + Barra de herramientas \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8c6370a00..d98508a06 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -243,13 +243,13 @@ Configura la tastiera Scuro Spaziatura dal bordo in basso - Colori (Tema scuro) + Colori personalizzati (tema scuro) Scuro Mostra solo i colori principali Colori dinamici Aspetto Sfondo tasti - Colori (tema chiaro) + Tema chiaro Sfondo dei tasti funzione La parola è già presente nel dizionario utente %s. Arrotondato @@ -275,7 +275,7 @@ Aggiungi una parola Testo dei suggerimenti Tocca per vedere l\'anteprima - Personalizzato (Tema chiaro) + Personalizzato (tema chiaro) Sostituisci dizionario Aggiungi a %s Sfondo della tastiera @@ -293,11 +293,11 @@ Sabbia Stile Mostra le funzionalità che potrebbero passare inosservate - Colori (tema scuro) + Tema scuro Mostra tutti i colori Seleziona i colori per il testo e gli sfondi Caratteri degli indicatori dei tasti - Personalizzato (Tema scuro) + Personalizzato (tema scuro) Marrone Vuoi davvero sostituire il dizionario aggiunto dall\'utente \"%1$s\"? \n @@ -316,7 +316,7 @@ Chiudi Traccia dell\'input gestuale Tocca la lingua per aprire le impostazioni - Colori (Tema chiaro) + Colori personalizzati (tema chiaro) Salva log Holo bianco Dizionario interno principale @@ -396,4 +396,5 @@ Mansi Mostra altri colori Questa impostazione mostra tutti i colori utilizzati internamente. L\'elenco dei colori può cambiare in qualsiasi momento. Non esiste un colore predefinito e i nomi non sono tradotti. + Emoji \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3cc51605b..b6c1f1753 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -167,7 +167,7 @@ "Zoeken" "Pauze" "Wacht" - Enkele handmodus + Bediening met één hand Roze Zand Violet @@ -359,7 +359,7 @@ \n\t• woord selecteren &#65515; alles selecteren<br> \n\t• ongedaan maken &#8596; opnieuw <br> <br> \n► Lang drukken van toetsen in de suggestieregel zet deze daarin vast. <br> <br> -\n► Lang drukken van de komma-toets om de klembordweergave, Emoji-weergave, Enkele handmodus, Instellingen, of de taal te wisselen: <br> +\n► Lang drukken van de komma-toets om de klembordweergave, Emoji-weergave, Bediening met één hand, Instellingen, of de taal te wisselen: <br> \n\t• Emoji-weergave en taal-schakelaar verdwijnen wanneer de corresponderende toets is geactiveerd; <br> \n\t• Voor sommige lay-outs is het niet de komma-toets, maar de toets op dezelfde positie (bijvoorbeeld \\\'q\\\' bij een Dvorak-lay-out). <br> <br> \n► Wanneer incognitomodus is ingeschakeld, worden er geen woorden geleerd en worden er geen emoji\'s aan de lijst recent toegevoegd.<br> <br> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fe776ca93..9f8bca2ea 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -52,10 +52,10 @@ Английский (Великобритания) Английский (США) Испанский (США) - хинглиш + Хинглиш Мансийский - кайтагский - сербский (латиница) + Кайтагский + Сербский (латиница) Английский (Великобритания) (%s) Английский (США) (%s) Испанский (США) (%s) @@ -65,7 +65,7 @@ Сербский (%s) %s (Традиционная) %s (Компактная) - стандартная (латиница) + Стандартная (латиница) Латиница (QWERTY) Латиница (QWERTZ) Латиница (AZERTY) @@ -348,4 +348,24 @@ Вырезать Поведение клавиши смены языка Эмодзи + Выбрать закрепленные клавиши панели инструментов + Это отключит остальные функции клавиш панели инструментов при длительном нажатии + Закреплять клавиши панели инструментов при длительном нажатии + Контент скопирован + Показывать панель инструментов автоматически + Скрывать панель инструментов автоматически + Всегда использовать среднее предложение + При нажатии точки или пунктуации будет использовано среднее предложение + Закрыть буфер обмена + Выбрать клавиши панели инструментов буфера обмена + %s (Extended) + Скрывать панель инструментов при появлении предложений + Панель инструментов + Показывать панель инструментов при вводе или при выборе текста + Показать больше цветов + Эта настройка показывает все цвета, используемые внутри приложения. Список цветов может меняться. Цвета по умолчанию нету и названия не будут переводиться. + Функциональные клавишы (дополнительные символы) + Изменить раскладку функциональных клавиш + Функциональные клавишы + Функциональные клавишы (символы) \ No newline at end of file diff --git a/fastlane/metadata/android/ar/changelogs/2002.txt b/fastlane/metadata/android/ar/changelogs/2002.txt new file mode 100644 index 000000000..3c0f5d64b --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/2002.txt @@ -0,0 +1,4 @@ +* إضافة مفتاح شريط أدوات الرموز التعبيرية، بواسطة @codokie (#845، #837) +* تحسينات فيما يتعلق بالحروف المكررة (#225 وربما غيرها) +* تجنب وضع المؤشر داخل الرموز التعبيرية (#859) +* إصلاحات طفيفة للميزات المضافة مؤخرا diff --git a/fastlane/metadata/android/en-US/changelogs/2003.txt b/fastlane/metadata/android/en-US/changelogs/2003.txt new file mode 100644 index 000000000..9bf1f6ba0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/2003.txt @@ -0,0 +1,11 @@ +* customizable functional key layout +* slightly adjust symbols and more symbols layouts +* basic support for alt, ctrl, fn, meta keys +* extend toolbar (long-press functionality, optional long-press pinning, auto-show/hide, better clipboard toolbar, ...) +* add tab key +* add caps lock indicator +* add layouts for some languages +* add toolbar keys as keyboard keys +* allow customizing all colors +* toast notification when copying text +* bug fixes and further improvements, see full release notes diff --git a/fastlane/metadata/android/nl-NL/changelogs/2002.txt b/fastlane/metadata/android/nl-NL/changelogs/2002.txt new file mode 100644 index 000000000..23ebee70e --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/2002.txt @@ -0,0 +1,4 @@ +* Emoji-werkbalktoets toegevoegd, door @codokie (#845, #837) +* verbeteringen met betrekking tot gedupliceerde letters (#225 en misschien anderen) +* Vermijd het plaatsen van de cursor in emoji's (#859) +* Kleine correcties voor recent toegevoegde functies diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt index 7a8a39a7b..62c2455cb 100644 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -23,7 +23,7 @@ Kenmerken:
  • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier downloaden (klik op het bestand en klik vervolgens op "raw" of de kleine download-knop)
  • Klembordgeschiedenis
  • -
  • Enkele handmodus
  • +
  • Bediening met één hand
  • Gesplitst toetsenbord (alleen beschikbaar als het scherm groot genoeg is)
  • Numpad
  • Back-up en herstel van instellingen en geleerde woorden / geschiedenisgegevens
  • From 4255dc2ce8052a7579dafb7ec79d309323536399 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 19:44:46 +0200 Subject: [PATCH 52/92] remove fillGapsWithSpacers was only necessary for old AOSP keyboard parser --- .../keyboard/internal/KeyboardBuilder.kt | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt index 399b4963b..da14f00bb 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt @@ -117,7 +117,6 @@ open class KeyboardBuilder(protected val mContext: Context, var currentY = mParams.mTopPadding.toFloat() for (row in keysInRows) { if (row.isEmpty()) continue - fillGapsWithSpacers(row) var currentX = mParams.mLeftPadding.toFloat() row.forEach { it.setAbsoluteDimensions(currentX, currentY) @@ -129,37 +128,6 @@ open class KeyboardBuilder(protected val mContext: Context, } } - // necessary for adjusting widths and positions properly - // without adding spacers whose width can then be adjusted, we would have to deal with keyXPos, - // which is more complicated than expected - // todo: remove? maybe was only necessary with old parser - private fun fillGapsWithSpacers(row: MutableList) { - if (mParams.mId.mElementId !in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) return - if (row.isEmpty()) return - if (row.all { it.xPos == 0f }) return // need existing xPos to determine gaps - var currentX = 0f + mParams.mLeftPadding - var i = 0 - while (i < row.size) { - val currentKeyXPos = row[i].xPos - if (currentKeyXPos > currentX) { - // insert spacer - val spacer = KeyParams.newSpacer(mParams, (currentKeyXPos - currentX) / mParams.mBaseWidth) - spacer.yPos = row[i].yPos - row.add(i, spacer) - i++ - currentX += currentKeyXPos - currentX - } - currentX += row[i].mAbsoluteWidth - i++ - } - if (currentX < mParams.mOccupiedWidth) { - // insert spacer - val spacer = KeyParams.newSpacer(mParams, (mParams.mOccupiedWidth - currentX) / mParams.mBaseWidth) - spacer.yPos = row.last().yPos - row.add(spacer) - } - } - private fun addSplit() { val spacerRelativeWidth = Settings.getInstance().current.mSplitKeyboardSpacerRelativeWidth // adjust gaps for the whole keyboard, so it's the same for all rows @@ -168,7 +136,6 @@ open class KeyboardBuilder(protected val mContext: Context, var maxWidthBeforeSpacer = 0f var maxWidthAfterSpacer = 0f for (row in keysInRows) { - fillGapsWithSpacers(row) val y = row.first().yPos // all have the same y, so this is fine val relativeWidthSum = row.sumOf { it.mWidth } // sum up relative widths val spacer = KeyParams.newSpacer(mParams, spacerRelativeWidth) From 6e520bf84cf4a76fdad19248488459ea1dd7affc Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 20:05:17 +0200 Subject: [PATCH 53/92] update PR workflow to run tests skip tests known to fail because of unfixed bugs avoids wasting time on the native build --- .github/workflows/build-test-auto.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test-auto.yml b/.github/workflows/build-test-auto.yml index 54f3d7fb5..19ddaee9b 100644 --- a/.github/workflows/build-test-auto.yml +++ b/.github/workflows/build-test-auto.yml @@ -33,7 +33,7 @@ jobs: run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew -Pandroid.injected.build.abi=arm64-v8a assembleDebug + run: ./gradlew testRunTestsUnitTest - name: Archive reports for failed job uses: actions/upload-artifact@v4 From d0983e6c3b093e7f8280f08b2476e988eae9eecb Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 20:48:34 +0200 Subject: [PATCH 54/92] stick to language key setting don't show langauge switch key in comma popups when there is nothing to switch fixes #897 --- .../internal/keyboard_parser/floris/TextKeyData.kt | 3 ++- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 9 +++++---- .../helium314/keyboard/latin/RichInputMethodManager.java | 8 ++++++++ .../latin/settings/PreferencesSettingsFragment.java | 3 ++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index fef79526f..02b615320 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -18,6 +18,7 @@ import helium314.keyboard.keyboard.internal.KeyboardParams import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyLabel.convertFlorisLabel import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyLabel.rtlLabel +import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.StringUtils @@ -121,7 +122,7 @@ sealed interface KeyData : AbstractKeyData { keys.add("!icon/clipboard_normal_key|!code/key_clipboard") if (!params.mId.mEmojiKeyEnabled && !params.mId.isNumberLayout) keys.add("!icon/emoji_normal_key|!code/key_emoji") - if (!params.mId.mLanguageSwitchKeyEnabled && !params.mId.isNumberLayout) + if (!params.mId.mLanguageSwitchKeyEnabled && !params.mId.isNumberLayout && RichInputMethodManager.getInstance().canSwitchLanguage()) keys.add("!icon/language_switch_key|!code/key_language_switch") if (!params.mId.mOneHandedModeEnabled) keys.add("!icon/start_onehanded_mode_key|!code/key_start_onehanded") diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 5eb7665a6..72b4fbfdf 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1412,10 +1412,11 @@ public void switchToNextSubtype() { if (switchIme && !switchSubtype && switchInputMethod()) return; final boolean hasMoreThanOneSubtype = mRichImm.getMyEnabledInputMethodSubtypeList(false).size() > 1; - // switch subtype if wanted and possible - if (switchSubtype && !switchIme && hasMoreThanOneSubtype) { - // switch to previous subtype if current one was used, otherwise cycle through list - mSubtypeState.switchSubtype(mRichImm); + // switch subtype if wanted, do nothing if no other subtype is available + if (switchSubtype && !switchIme) { + if (hasMoreThanOneSubtype) + // switch to previous subtype if current one was used, otherwise cycle through list + mSubtypeState.switchSubtype(mRichImm); return; } // language key set to switch both, or language key is not shown on keyboard -> switch both diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java index a4bc4a432..def47a181 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java @@ -360,6 +360,14 @@ private void updateCurrentSubtype(final InputMethodSubtype subtype) { mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); } + public boolean canSwitchLanguage() { + if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherSubtypes && hasMultipleEnabledSubtypesInThisIme(false)) + return true; + if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherImes && mImm.getEnabledInputMethodList().size() > 1) + return true; + return false; + } + // todo: is shortcutIme only voice input, or can it be something else? // if always voice input, rename it and other things like mHasShortcutKey private void updateShortcutIme() { diff --git a/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java b/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java index ea2269cee..a0da00735 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java @@ -77,7 +77,8 @@ public void onSharedPreferenceChanged(final SharedPreferences prefs, final Strin if (key == null) return; switch (key) { case Settings.PREF_POPUP_KEYS_ORDER, Settings.PREF_SHOW_POPUP_HINTS, Settings.PREF_SHOW_NUMBER_ROW, - Settings.PREF_POPUP_KEYS_LABELS_ORDER -> mReloadKeyboard = true; + Settings.PREF_POPUP_KEYS_LABELS_ORDER, Settings.PREF_LANGUAGE_SWITCH_KEY, + Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY -> mReloadKeyboard = true; case Settings.PREF_LOCALIZED_NUMBER_ROW -> KeyboardLayoutSet.onSystemLocaleChanged(); case Settings.PREF_SHOW_HINTS -> findPreference(Settings.PREF_POPUP_KEYS_LABELS_ORDER).setVisible(prefs.getBoolean(Settings.PREF_SHOW_HINTS, false)); From 3b30864b00c25f3a4f7313a3c782635fc0c79dea Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 24 Jun 2024 20:58:12 +0200 Subject: [PATCH 55/92] correctly set placeholder default width --- .../keyboard/internal/keyboard_parser/floris/TextKeyData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 02b615320..20de0f760 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -370,9 +370,9 @@ sealed interface KeyData : AbstractKeyData { /** this expects that codes and labels are already converted from FlorisBoard values, usually through compute */ fun toKeyParams(params: KeyboardParams, additionalLabelFlags: Int = 0): Key.KeyParams { - if (type == KeyType.PLACEHOLDER) return Key.KeyParams.newSpacer(params, width) - val newWidth = if (width == 0f) getDefaultWidth(params) else width + if (type == KeyType.PLACEHOLDER) return Key.KeyParams.newSpacer(params, newWidth) + val newCode: Int val newLabel: String if (code in KeyCode.Spec.CURRENCY) { From 4aac81391e9e505724a315585d4de13b55767c0a Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 25 Jun 2024 18:42:26 +0200 Subject: [PATCH 56/92] set random (but fixed) default colors for "all colors", see #903 --- .../java/helium314/keyboard/latin/common/Colors.kt | 4 +++- .../keyboard/latin/settings/ColorsSettingsFragment.kt | 11 +++++++---- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/common/Colors.kt b/app/src/main/java/helium314/keyboard/latin/common/Colors.kt index 4ad873e4f..80ea36b89 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/Colors.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/Colors.kt @@ -552,7 +552,7 @@ class AllColors(private val colorMap: EnumMap, override val them private val stateListMap = EnumMap(ColorType::class.java) private var backgroundSetupDone = false private val colorFilters = hashMapOf() - override fun get(color: ColorType): Int = colorMap[color] ?: Color.GRAY + override fun get(color: ColorType): Int = colorMap[color] ?: color.default() override fun setColor(drawable: Drawable, color: ColorType) { val colorStateList = stateListMap.getOrPut(color) { stateList(brightenOrDarken(get(color), true), get(color)) } @@ -668,3 +668,5 @@ enum class ColorType { TOOL_BAR_KEY_ENABLED_BACKGROUND, MAIN_BACKGROUND, } + +fun ColorType.default() = ColorUtils.setAlphaComponent(name.hashCode() and 0xffffff, 255) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/ColorsSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/ColorsSettingsFragment.kt index c39c963ff..6f005d347 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/ColorsSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/ColorsSettingsFragment.kt @@ -2,7 +2,6 @@ package helium314.keyboard.latin.settings import android.content.res.Configuration -import android.graphics.Color import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -17,12 +16,14 @@ import androidx.core.content.edit import androidx.core.view.MenuProvider import androidx.core.view.forEach import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.rarepebble.colorpicker.ColorPickerView import helium314.keyboard.keyboard.KeyboardSwitcher import helium314.keyboard.latin.R import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.ColorType +import helium314.keyboard.latin.common.default import helium314.keyboard.latin.common.readAllColorsMap import helium314.keyboard.latin.common.writeAllColorsMap import helium314.keyboard.latin.databinding.ColorSettingBinding @@ -107,7 +108,6 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi reloadKeyboard(false) } moreColors = menuItem.itemId - binding.info.isGone = menuItem.itemId != 2 updateColorPrefs() return true } @@ -128,9 +128,11 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi } private fun showAllColors() { + binding.info.isVisible = true val colors = readAllColorsMap(prefs, isNight) ColorType.entries.forEach { type -> - val color = colors[type] ?: Color.GRAY + val color = colors[type] ?: type.default() + val csb = ColorSettingBinding.inflate(layoutInflater, binding.colorSettingsContainer, true) csb.root.tag = type csb.colorSwitch.isGone = true @@ -174,6 +176,7 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi } private fun showMainColors() { + binding.info.isGone = true val prefPrefix = if (isNight) Settings.PREF_THEME_USER_COLOR_NIGHT_PREFIX else Settings.PREF_THEME_USER_COLOR_PREFIX colorPrefsAndNames.forEachIndexed { index, (colorPref, colorPrefName) -> val autoColor = prefs.getBoolean(prefPrefix + colorPref + Settings.PREF_AUTO_USER_COLOR_SUFFIX, true) @@ -276,7 +279,7 @@ open class ColorsSettingsFragment : Fragment(R.layout.color_settings), MenuProvi val colorMap = readAllColorsMap(prefs, isNight) binding.colorSettingsContainer.forEach { view -> val type = view.tag as? ColorType ?: return@forEach - val color = colorMap[type] ?: Color.GRAY + val color = colorMap[type] ?: type.default() view.findViewById(R.id.color_preview)?.setColorFilter(color) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe8f3f71c..f76d50917 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -714,7 +714,7 @@ New dictionary: Show all colors - This setting exposes all colors that are used internally. The list of colors may change at any time. There is no default color, and the names will not be translated. + This setting exposes all colors that are used internally. The list of colors may change at any time. The default color is random, and the names will not be translated. Click for preview From efd7d53ca1ad86e628c3267c019530eff6f37abb Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:00:06 -0600 Subject: [PATCH 57/92] Option to customize start lag for gestures during fast typing (#894) fixes #882 --- .../GestureStrokeRecognitionParams.java | 2 +- .../GestureStrokeRecognitionPoints.java | 3 +- .../settings/GestureSettingsFragment.java | 43 +++++++++++++++++++ .../keyboard/latin/settings/Settings.java | 12 ++++++ .../latin/settings/SettingsValues.java | 2 + app/src/main/res/values/config-common.xml | 1 + app/src/main/res/values/strings.xml | 6 ++- app/src/main/res/xml/prefs_screen_gesture.xml | 6 +++ 8 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionParams.java b/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionParams.java index 51352af1f..56630ddf5 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionParams.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionParams.java @@ -40,7 +40,7 @@ public final class GestureStrokeRecognitionParams { private GestureStrokeRecognitionParams() { // These parameter values are default and intended for testing. - mStaticTimeThresholdAfterFastTyping = 350; // msec + mStaticTimeThresholdAfterFastTyping = 500; // msec mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth/sec mDynamicThresholdDecayDuration = 450; // msec mDynamicTimeThresholdFrom = 300; // msec diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionPoints.java b/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionPoints.java index 984fc8bcd..f5f7a3364 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionPoints.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/GestureStrokeRecognitionPoints.java @@ -6,6 +6,7 @@ package helium314.keyboard.keyboard.internal; +import helium314.keyboard.latin.settings.Settings; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.common.Constants; @@ -104,7 +105,7 @@ public int getLength() { public void addDownEventPoint(final int x, final int y, final int elapsedTimeSinceFirstDown, final int elapsedTimeSinceLastTyping) { reset(); - if (elapsedTimeSinceLastTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) { + if (elapsedTimeSinceLastTyping < Settings.getInstance().getCurrent().mGestureFastTypingCooldown) { mAfterFastTyping = true; } if (DEBUG) { diff --git a/app/src/main/java/helium314/keyboard/latin/settings/GestureSettingsFragment.java b/app/src/main/java/helium314/keyboard/latin/settings/GestureSettingsFragment.java index 79fd8b7ca..225eefdbe 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/GestureSettingsFragment.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/GestureSettingsFragment.java @@ -7,6 +7,7 @@ package helium314.keyboard.latin.settings; import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Bundle; import helium314.keyboard.latin.R; @@ -25,6 +26,7 @@ public final class GestureSettingsFragment extends SubScreenFragment { public void onCreate(final Bundle icicle) { super.onCreate(icicle); addPreferencesFromResource(R.xml.prefs_screen_gesture); + setupGestureFastTypingCooldownPref(); refreshSettingsEnablement(); } @@ -38,5 +40,46 @@ private void refreshSettingsEnablement() { setPreferenceVisible(Settings.PREF_GESTURE_PREVIEW_TRAIL, Settings.readGestureInputEnabled(prefs)); setPreferenceVisible(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, Settings.readGestureInputEnabled(prefs)); setPreferenceVisible(Settings.PREF_GESTURE_SPACE_AWARE, Settings.readGestureInputEnabled(prefs)); + setPreferenceVisible(Settings.PREF_GESTURE_FAST_TYPING_COOLDOWN, Settings.readGestureInputEnabled(prefs)); + } + + private void setupGestureFastTypingCooldownPref() { + final SeekBarDialogPreference pref = findPreference( + Settings.PREF_GESTURE_FAST_TYPING_COOLDOWN); + if (pref == null) return; + final SharedPreferences prefs = getSharedPreferences(); + final Resources res = getResources(); + pref.setInterface(new SeekBarDialogPreference.ValueProxy() { + @Override + public void writeValue(final int value, final String key) { + prefs.edit().putInt(key, value).apply(); + } + + @Override + public void writeDefaultValue(final String key) { + prefs.edit().remove(key).apply(); + } + + @Override + public int readValue(final String key) { + return Settings.readGestureFastTypingCooldown(prefs, res); + } + + @Override + public int readDefaultValue(final String key) { + return Settings.readDefaultGestureFastTypingCooldown(res); + } + + @Override + public String getValueText(final int value) { + if (value == 0) { + return res.getString(R.string.gesture_fast_typing_cooldown_instant); + } + return res.getString(R.string.abbreviation_unit_milliseconds, String.valueOf(value)); + } + + @Override + public void feedbackValue(final int value) {} + }); } } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index f352a742a..ebaa1c7a1 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -115,6 +115,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_GESTURE_PREVIEW_TRAIL = "gesture_preview_trail"; public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = "gesture_floating_preview_text"; public static final String PREF_GESTURE_SPACE_AWARE = "gesture_space_aware"; + public static final String PREF_GESTURE_FAST_TYPING_COOLDOWN = "gesture_fast_typing_cooldown"; public static final String PREF_SHOW_SETUP_WIZARD_ICON = "show_setup_wizard_icon"; public static final String PREF_USE_CONTACTS = "use_contacts"; public static final String PREFS_LONG_PRESS_SYMBOLS_FOR_NUMPAD = "long_press_symbols_for_numpad"; @@ -364,6 +365,17 @@ public static int readDefaultClipboardHistoryRetentionTime(final Resources res) return res.getInteger(R.integer.config_clipboard_history_retention_time); } + public static int readGestureFastTypingCooldown(final SharedPreferences prefs, final Resources res) { + final int milliseconds = prefs.getInt( + PREF_GESTURE_FAST_TYPING_COOLDOWN, UNDEFINED_PREFERENCE_VALUE_INT); + return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds + : readDefaultGestureFastTypingCooldown(res); + } + + public static int readDefaultGestureFastTypingCooldown(final Resources res) { + return res.getInteger(R.integer.config_gesture_static_time_threshold_after_fast_typing); + } + public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) { return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, "none")) { case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index 0b37363f2..d93ce79cd 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -95,6 +95,7 @@ public class SettingsValues { public final boolean mGestureInputEnabled; public final boolean mGestureTrailEnabled; public final boolean mGestureFloatingPreviewTextEnabled; + public final int mGestureFastTypingCooldown; public final boolean mSlidingKeyInputPreviewEnabled; public final int mKeyLongpressTimeout; public final boolean mEnableEmojiAltPhysicalKey; @@ -199,6 +200,7 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mAccount = null; // remove? or can it be useful somewhere? mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); + mGestureFastTypingCooldown = Settings.readGestureFastTypingCooldown(prefs, res); mOverrideShowingSuggestions = mInputAttributes.mMayOverrideShowingSuggestions && readSuggestionsOverrideEnabled(prefs); mSuggestionsEnabledPerUserSettings = (mInputAttributes.mShouldShowSuggestions && readSuggestionsEnabled(prefs)) || mOverrideShowingSuggestions; diff --git a/app/src/main/res/values/config-common.xml b/app/src/main/res/values/config-common.xml index b2b52e1d1..ee30cee46 100644 --- a/app/src/main/res/values/config-common.xml +++ b/app/src/main/res/values/config-common.xml @@ -57,6 +57,7 @@ 20 500 + 10 150% diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f76d50917..7966baa35 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -146,8 +146,12 @@ See the suggested word while gesturing Phrase gesture - + Input spaces during gestures by gliding to the space key + + Rapid typing cooldown + + Always start instantly Enable clipboard history diff --git a/app/src/main/res/xml/prefs_screen_gesture.xml b/app/src/main/res/xml/prefs_screen_gesture.xml index 01f0141f6..6e34a6043 100644 --- a/app/src/main/res/xml/prefs_screen_gesture.xml +++ b/app/src/main/res/xml/prefs_screen_gesture.xml @@ -6,6 +6,7 @@ --> + From cec47bf03d331a401c1fab903a46782dc39624a1 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 29 Jun 2024 15:08:18 +0200 Subject: [PATCH 58/92] adjust templates and stuff --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ++++- .github/ISSUE_TEMPLATE/feature_request.md | 5 ++++- .github/ISSUE_TEMPLATE/other.md | 2 +- .github/ISSUE_TEMPLATE/spell_checker.md | 16 ---------------- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- CONTRIBUTING.md | 2 +- layouts.md | 6 ++++-- 7 files changed, 16 insertions(+), 24 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/spell_checker.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ac6e9b481..261f74d45 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,10 @@ tl;dr: * a single issue per topic * reduce screenshot size - + **Describe the bug** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 8a6c702ca..e3fd107e3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,7 +10,10 @@ tl;dr: * a single issue per topic * reduce screenshot size - + **Is your feature request related to a problem? Please describe.** diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index 3956154a8..cd7b5135a 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -9,4 +9,4 @@ tl;dr: * a single issue per topic * reduce screenshot size - + diff --git a/.github/ISSUE_TEMPLATE/spell_checker.md b/.github/ISSUE_TEMPLATE/spell_checker.md deleted file mode 100644 index 629a69749..000000000 --- a/.github/ISSUE_TEMPLATE/spell_checker.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Spell checker issue -about: Issue related to spell checker (this is the component that underlines mis-spelled words in red) -labels: "spell checker" ---- - -Please see the appropriate readme section for issue reporting guidelines: https://github.com/Helium314/HeliBoard?tab=readme-ov-file#reporting-issues -tl;dr: -* search for duplicates, also in closed issues -* a single issue per topic -* reduce screenshot size - -Make sure you actually enabled HeliBoard spell checker. Usually it can be found in System Settings -> System -> Languages -> Advanced -> Spell Checker, but this may depend on Android version. -Note that the menu when tapping a word underlined in red is coming form the Android system and is not under control of HeliBoard. - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8756a0530..d862f0241 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -See the contributing readme for more detailed guideline: https://github.com/Helium314/HeliBoard/blob/main/CONTRIBUTING.md +See the contributing readme for more detailed guideline, please understand and accept them: https://github.com/Helium314/HeliBoard/blob/main/CONTRIBUTING.md tl;dr (you should still read the full list though): * make sure it's wanted * a single thing only @@ -13,4 +13,4 @@ Further * If you add a keyboard layout, make sure you have read https://github.com/Helium314/HeliBoard/blob/main/layouts.md#adding-new-layouts--languages * Please avoid force-pushing when doing changes. This way it's not possible which parts have changed since the previous state. -(please remove the template text before submitting the PR) \ No newline at end of file + \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 675a84177..63716dfb8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ HeliBoard is a complex application, when contributing, you must take a step back If your contribution contains code that is not your own, provide a link to the source. - **Complies with the user privacy principle HeliBoard follows**. -(and please leave dependency upgrades to the maintainers, unless it's an actual security issue) +(and leave dependency upgrades to the maintainers, unless it's an actual security issue) In addition to previous elements, HeliBoard must stick to [F-Droid inclusion guidelines](https://f-droid.org/docs/Inclusion_Policy/). # Adding Layouts diff --git a/layouts.md b/layouts.md index a4824c6b9..8aa78ebc0 100644 --- a/layouts.md +++ b/layouts.md @@ -9,20 +9,22 @@ Adding too many keys or too long texts will make the keyboard look awkward or br There are some sanity checks when adding a layout to avoid such issues, but they do not cover all possible cases. Further there is no check whether the layout actually contains characters of the selected language. -If you use an external glide typing library, you likely will have issues if your layout contains duplicate keys, or keys with text longer than a single letter. +If you use an external glide typing library, you likely will have issues if your layout contains duplicate keys, or keys with text longer than a single character. If the layout has exactly 2 keys in the bottom row, these keys will replace comma and period keys. More exactly: the first key will replace the first functional key with `"groupId": 1` in the bottom row, and the second key with replace the first key with `"groupId": 2`. ## Simple format * One key per line * Key format: [label] [popup keys], all separated by space, e.g. `a 0 + *` will create a key with text `a`, and the keys `0`, `+`, and `*` on long press + * see [below](#labels) for information about special labels * Two consecutive newlines mark beginning of a new row ## Json format +* Normal json layout with [lenient](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html) parsing, and ignoring lines starting with `//`. + * For anything else than small changes and copy/pasting text the in-app editor is unsuitable. A proper text editor (e.g. Kate or Notepad++) can significantly simplify work on json files. * Allows more flexibility than the simple format, e.g. changing keys depending on input type, shift state or layout direction * You can use character layouts from [FlorisBoard](https://github.com/florisboard/florisboard/blob/master/CONTRIBUTING.md#adding-the-layout) * Support is not 100% there yet, notably `kana_selector` and `char_width_selector` do not work. -* Lines _starting_ with `//` are ignored. * There is no need for specifying a `code`, it will be determined from the label automatically * You can still specify it, but it's only necessary if you want key label and code to be different (please avoid contributing layout with unnecessary codes to HeliBoard) * Note that not all _special codes_ (negative numbers) from FlorisBoard are supported From d7cb655c21bceb0d401c82d7dcac0d2f82278335 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 29 Jun 2024 17:25:24 +0200 Subject: [PATCH 59/92] reload keyboard when next-word suggestion setting is changed fixes #76 --- .../keyboard/latin/settings/CorrectionSettingsFragment.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/CorrectionSettingsFragment.java b/app/src/main/java/helium314/keyboard/latin/settings/CorrectionSettingsFragment.java index 08b4346ff..0c238fac3 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/CorrectionSettingsFragment.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/CorrectionSettingsFragment.java @@ -16,6 +16,7 @@ import androidx.preference.SwitchPreference; import androidx.preference.TwoStatePreference; +import helium314.keyboard.keyboard.KeyboardSwitcher; import helium314.keyboard.latin.R; import helium314.keyboard.latin.permissions.PermissionsManager; import helium314.keyboard.latin.permissions.PermissionsUtil; @@ -52,6 +53,8 @@ public void onSharedPreferenceChanged(final SharedPreferences prefs, final Strin .show(); } else if (Settings.PREF_SHOW_SUGGESTIONS.equals(key) && !prefs.getBoolean(key, true)) { ((TwoStatePreference)findPreference(Settings.PREF_ALWAYS_SHOW_SUGGESTIONS)).setChecked(false); + } else if (Settings.PREF_BIGRAM_PREDICTIONS.equals(key)) { + KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext()); } refreshEnabledSettings(); } From f7d82b9589dec6eb5e819526c0abe626592260ab Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 29 Jun 2024 20:34:32 +0200 Subject: [PATCH 60/92] allow customizing currency keys, fixes #353 --- .../keyboard_parser/LocaleKeyboardInfos.kt | 5 ++++ .../settings/AdvancedSettingsFragment.kt | 30 +++++++++++++++++++ .../keyboard/latin/settings/Settings.java | 5 ++++ app/src/main/res/values/strings.xml | 4 +++ .../main/res/xml/prefs_screen_advanced.xml | 5 ++++ 5 files changed, 49 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index 0cbd84712..aeda0105b 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -274,6 +274,11 @@ private const val READER_MODE_NUMBER_ROW = 4 // probably could be improved and extended, currently this is what's done in key_styles_currency.xml private fun getCurrencyKey(locale: Locale): Pair> { + Settings.getInstance().readCustomCurrencyKey().takeIf { it.isNotBlank() }?.let { + val split = it.trim().splitOnWhitespace() + if (split.isNotEmpty()) + return split[0] to (split.toSet() + genericCurrencyPopupKeys).filterNot { it == split[0] } + } if (locale.country.matches(euroCountries)) return euro if (locale.toString().matches(euroLocales)) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt index 3669fec44..79988c0d9 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt @@ -12,9 +12,13 @@ import android.content.SharedPreferences import android.net.Uri import android.os.Build import android.os.Bundle +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView import helium314.keyboard.latin.utils.Log import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.preference.Preference import androidx.preference.PreferenceManager import kotlinx.serialization.encodeToString @@ -48,6 +52,7 @@ import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.ExecutorUtils import helium314.keyboard.latin.utils.JniUtils +import helium314.keyboard.latin.utils.ResourceUtils import helium314.keyboard.latin.utils.editCustomLayout import helium314.keyboard.latin.utils.getCustomLayoutFiles import helium314.keyboard.latin.utils.getStringResourceOrName @@ -137,6 +142,11 @@ class AdvancedSettingsFragment : SubScreenFragment() { showCustomizeFunctionalKeyLayoutsDialog() true } + + findPreference(Settings.PREF_CUSTOM_CURRENCY_KEY)?.setOnPreferenceClickListener { + customCurrencyDialog() + true + } } override fun onStart() { @@ -451,6 +461,26 @@ class AdvancedSettingsFragment : SubScreenFragment() { } } + private fun customCurrencyDialog() { + val layout = LinearLayout(requireContext()) + layout.orientation = LinearLayout.VERTICAL + layout.addView(TextView(requireContext()).apply { setText(R.string.customize_currencies_detail) }) + val et = EditText(requireContext()).apply { setText(sharedPreferences.getString(Settings.PREF_CUSTOM_CURRENCY_KEY, "")) } + layout.addView(et) + val padding = ResourceUtils.toPx(8, resources) + layout.setPadding(3 * padding, padding, padding, padding) + AlertDialog.Builder(requireContext()) + .setTitle(R.string.customize_currencies) + .setView(layout) + .setPositiveButton(android.R.string.ok) { _, _ -> + sharedPreferences.edit { putString(Settings.PREF_CUSTOM_CURRENCY_KEY, et.text.toString()) } + KeyboardLayoutSet.onSystemLocaleChanged() + } + .setNegativeButton(android.R.string.cancel, null) + .setNeutralButton(R.string.button_default) { _, _ -> sharedPreferences.edit { putString(Settings.PREF_CUSTOM_CURRENCY_KEY, "") } } + .show() + } + private fun setupKeyLongpressTimeoutSettings() { val prefs = sharedPreferences findPreference(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy { diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index ebaa1c7a1..0344073ff 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -127,6 +127,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_SHOW_NUMBER_ROW = "show_number_row"; public static final String PREF_LOCALIZED_NUMBER_ROW = "localized_number_row"; + public static final String PREF_CUSTOM_CURRENCY_KEY = "custom_currency_key"; public static final String PREF_SHOW_HINTS = "show_hints"; public static final String PREF_POPUP_KEYS_ORDER = "popup_keys_order"; @@ -661,4 +662,8 @@ public int getStringResIdByName(final String name) { public String getInLocale(@StringRes final int resId, final Locale locale) { return RunInLocaleKt.runInLocale(mContext, locale, (ctx) -> ctx.getString(resId)); } + + public String readCustomCurrencyKey() { + return mPrefs.getString(PREF_CUSTOM_CURRENCY_KEY, ""); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7966baa35..accb00bc6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,6 +501,10 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM Numpad (landscape) Set background image + + Customize currencies + + Set main and secondary currency symbols, separated with space Set image for day or night mode? diff --git a/app/src/main/res/xml/prefs_screen_advanced.xml b/app/src/main/res/xml/prefs_screen_advanced.xml index 80422afbe..d615c755c 100644 --- a/app/src/main/res/xml/prefs_screen_advanced.xml +++ b/app/src/main/res/xml/prefs_screen_advanced.xml @@ -76,6 +76,11 @@ android:defaultValue="true" android:persistent="true" /> + + Date: Sun, 30 Jun 2024 00:35:12 -0600 Subject: [PATCH 61/92] Upscale the d-pad arrows globally (#934) --- .../keyboard/latin/utils/ToolbarUtils.kt | 5 ----- .../{ic_arrow_down.xml => ic_dpad_down.xml} | 2 +- ...wn_rounded.xml => ic_dpad_down_rounded.xml} | 2 +- app/src/main/res/drawable/ic_dpad_left.xml | 17 +++++++++++++++++ .../main/res/drawable/ic_dpad_left_rounded.xml | 18 ++++++++++++++++++ app/src/main/res/drawable/ic_dpad_right.xml | 5 +++++ .../res/drawable/ic_dpad_right_rounded.xml | 5 +++++ .../{ic_arrow_up.xml => ic_dpad_up.xml} | 3 ++- ...w_up_rounded.xml => ic_dpad_up_rounded.xml} | 2 +- .../res/drawable/sym_keyboard_shift_lxx.xml | 7 ++++--- .../drawable/sym_keyboard_shift_rounded.xml | 7 ++++--- .../sym_keyboard_switch_onehanded_holo.xml | 13 ------------- .../main/res/values/keyboard-icons-holo.xml | 10 +++++----- .../res/values/keyboard-icons-lxx-light.xml | 8 ++++---- .../main/res/values/keyboard-icons-rounded.xml | 8 ++++---- 15 files changed, 71 insertions(+), 41 deletions(-) rename app/src/main/res/drawable/{ic_arrow_down.xml => ic_dpad_down.xml} (75%) rename app/src/main/res/drawable/{ic_arrow_down_rounded.xml => ic_dpad_down_rounded.xml} (72%) create mode 100644 app/src/main/res/drawable/ic_dpad_left.xml create mode 100644 app/src/main/res/drawable/ic_dpad_left_rounded.xml create mode 100644 app/src/main/res/drawable/ic_dpad_right.xml create mode 100644 app/src/main/res/drawable/ic_dpad_right_rounded.xml rename app/src/main/res/drawable/{ic_arrow_up.xml => ic_dpad_up.xml} (55%) rename app/src/main/res/drawable/{ic_arrow_up_rounded.xml => ic_dpad_up_rounded.xml} (72%) delete mode 100644 app/src/main/res/drawable/sym_keyboard_switch_onehanded_holo.xml diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index dcb09294d..4f8b05f0f 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -24,11 +24,6 @@ fun createToolbarKey(context: Context, keyboardAttr: TypedArray, key: ToolbarKey val contentDescriptionId = context.resources.getIdentifier(key.name.lowercase(), "string", context.packageName) if (contentDescriptionId != 0) button.contentDescription = context.getString(contentDescriptionId) - if (key == LEFT || key == RIGHT || key == UP || key == DOWN) { - // arrows look a little awkward when not scaled - button.scaleX = 1.2f - button.scaleY = 1.2f - } button.isActivated = !when (key) { INCOGNITO -> Settings.readAlwaysIncognitoMode(DeviceProtectedUtils.getSharedPreferences(context)) ONE_HANDED -> Settings.getInstance().current.mOneHandedModeEnabled diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_dpad_down.xml similarity index 75% rename from app/src/main/res/drawable/ic_arrow_down.xml rename to app/src/main/res/drawable/ic_dpad_down.xml index e112dc055..8b329d1c2 100644 --- a/app/src/main/res/drawable/ic_arrow_down.xml +++ b/app/src/main/res/drawable/ic_dpad_down.xml @@ -1,5 +1,5 @@ + android:drawable="@drawable/ic_dpad_left"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_down_rounded.xml b/app/src/main/res/drawable/ic_dpad_down_rounded.xml similarity index 72% rename from app/src/main/res/drawable/ic_arrow_down_rounded.xml rename to app/src/main/res/drawable/ic_dpad_down_rounded.xml index 74d51614f..dcd28503f 100644 --- a/app/src/main/res/drawable/ic_arrow_down_rounded.xml +++ b/app/src/main/res/drawable/ic_dpad_down_rounded.xml @@ -1,5 +1,5 @@ + android:drawable="@drawable/ic_dpad_left_rounded"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dpad_left.xml b/app/src/main/res/drawable/ic_dpad_left.xml new file mode 100644 index 000000000..a8f01bece --- /dev/null +++ b/app/src/main/res/drawable/ic_dpad_left.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dpad_left_rounded.xml b/app/src/main/res/drawable/ic_dpad_left_rounded.xml new file mode 100644 index 000000000..048ce37c3 --- /dev/null +++ b/app/src/main/res/drawable/ic_dpad_left_rounded.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dpad_right.xml b/app/src/main/res/drawable/ic_dpad_right.xml new file mode 100644 index 000000000..3e780da8c --- /dev/null +++ b/app/src/main/res/drawable/ic_dpad_right.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_dpad_right_rounded.xml b/app/src/main/res/drawable/ic_dpad_right_rounded.xml new file mode 100644 index 000000000..058416180 --- /dev/null +++ b/app/src/main/res/drawable/ic_dpad_right_rounded.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_dpad_up.xml similarity index 55% rename from app/src/main/res/drawable/ic_arrow_up.xml rename to app/src/main/res/drawable/ic_dpad_up.xml index 2b87db0be..f1b58af28 100644 --- a/app/src/main/res/drawable/ic_arrow_up.xml +++ b/app/src/main/res/drawable/ic_dpad_up.xml @@ -1,4 +1,5 @@ + + android:drawable="@drawable/ic_dpad_left"> diff --git a/app/src/main/res/drawable/ic_arrow_up_rounded.xml b/app/src/main/res/drawable/ic_dpad_up_rounded.xml similarity index 72% rename from app/src/main/res/drawable/ic_arrow_up_rounded.xml rename to app/src/main/res/drawable/ic_dpad_up_rounded.xml index 7f601c4fa..5a91ef348 100644 --- a/app/src/main/res/drawable/ic_arrow_up_rounded.xml +++ b/app/src/main/res/drawable/ic_dpad_up_rounded.xml @@ -1,5 +1,5 @@ + android:drawable="@drawable/ic_dpad_left_rounded"> \ No newline at end of file diff --git a/app/src/main/res/drawable/sym_keyboard_shift_lxx.xml b/app/src/main/res/drawable/sym_keyboard_shift_lxx.xml index 5c4b54897..7c31427bd 100644 --- a/app/src/main/res/drawable/sym_keyboard_shift_lxx.xml +++ b/app/src/main/res/drawable/sym_keyboard_shift_lxx.xml @@ -1,4 +1,5 @@ - + - + android:fromDegrees="90" + android:drawable="@drawable/ic_arrow_left"> + \ No newline at end of file diff --git a/app/src/main/res/drawable/sym_keyboard_shift_rounded.xml b/app/src/main/res/drawable/sym_keyboard_shift_rounded.xml index d3df2e6f0..7f601c4fa 100644 --- a/app/src/main/res/drawable/sym_keyboard_shift_rounded.xml +++ b/app/src/main/res/drawable/sym_keyboard_shift_rounded.xml @@ -1,4 +1,5 @@ - + - + android:fromDegrees="90" + android:drawable="@drawable/ic_arrow_left_rounded"> + \ No newline at end of file diff --git a/app/src/main/res/drawable/sym_keyboard_switch_onehanded_holo.xml b/app/src/main/res/drawable/sym_keyboard_switch_onehanded_holo.xml deleted file mode 100644 index cbf7391e8..000000000 --- a/app/src/main/res/drawable/sym_keyboard_switch_onehanded_holo.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/values/keyboard-icons-holo.xml b/app/src/main/res/values/keyboard-icons-holo.xml index 1eae6d2d7..58a520487 100644 --- a/app/src/main/res/values/keyboard-icons-holo.xml +++ b/app/src/main/res/values/keyboard-icons-holo.xml @@ -34,15 +34,15 @@ @drawable/sym_keyboard_clear_clipboard_holo @drawable/sym_keyboard_start_onehanded_holo @drawable/sym_keyboard_stop_onehanded_holo - @drawable/sym_keyboard_switch_onehanded_holo + @drawable/ic_arrow_left @drawable/ic_arrow_horizontal @drawable/sym_keyboard_numpad_key_holo @drawable/ic_arrow_right @drawable/ic_select_all - @drawable/ic_arrow_left - @drawable/ic_arrow_right - @drawable/ic_arrow_up - @drawable/ic_arrow_down + @drawable/ic_dpad_left + @drawable/ic_dpad_right + @drawable/ic_dpad_up + @drawable/ic_dpad_down @drawable/ic_delete @drawable/ic_undo @drawable/ic_redo diff --git a/app/src/main/res/values/keyboard-icons-lxx-light.xml b/app/src/main/res/values/keyboard-icons-lxx-light.xml index f673403fe..237a3e864 100644 --- a/app/src/main/res/values/keyboard-icons-lxx-light.xml +++ b/app/src/main/res/values/keyboard-icons-lxx-light.xml @@ -44,10 +44,10 @@ @drawable/sym_keyboard_numpad_key_lxx @drawable/ic_arrow_right @drawable/ic_select_all - @drawable/ic_arrow_left - @drawable/ic_arrow_right - @drawable/ic_arrow_up - @drawable/ic_arrow_down + @drawable/ic_dpad_left + @drawable/ic_dpad_right + @drawable/ic_dpad_up + @drawable/ic_dpad_down @drawable/ic_delete @drawable/ic_undo @drawable/ic_redo diff --git a/app/src/main/res/values/keyboard-icons-rounded.xml b/app/src/main/res/values/keyboard-icons-rounded.xml index 105d73f4e..dcf571c4f 100644 --- a/app/src/main/res/values/keyboard-icons-rounded.xml +++ b/app/src/main/res/values/keyboard-icons-rounded.xml @@ -43,10 +43,10 @@ @drawable/sym_keyboard_numpad_key_lxx @drawable/ic_arrow_right_rounded @drawable/ic_select_all_rounded - @drawable/ic_arrow_left_rounded - @drawable/ic_arrow_right_rounded - @drawable/ic_arrow_up_rounded - @drawable/ic_arrow_down_rounded + @drawable/ic_dpad_left_rounded + @drawable/ic_dpad_right_rounded + @drawable/ic_dpad_up_rounded + @drawable/ic_dpad_down_rounded @drawable/ic_delete_rounded @drawable/ic_undo_rounded @drawable/ic_redo_rounded From 05e9af9bc6494c972368c4ef6bdcef7ef5c4a8d6 Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Sun, 30 Jun 2024 00:39:08 -0600 Subject: [PATCH 62/92] Reduce longpress timeout for shift (caps lock) and symbols (for numpad) (#924) --- .../helium314/keyboard/keyboard/PointerTracker.java | 13 +++++-------- app/src/main/res/values/attrs.xml | 2 -- app/src/main/res/values/config-common.xml | 3 --- app/src/main/res/values/themes-common.xml | 1 - 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java index 6feb056a3..e476352fa 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java @@ -56,7 +56,6 @@ static final class PointerTrackerParams { public final int mSuppressKeyPreviewAfterBatchInputDuration; public final int mKeyRepeatStartTimeout; public final int mKeyRepeatInterval; - public final int mLongPressShiftLockTimeout; public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean( @@ -71,8 +70,6 @@ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); mKeyRepeatInterval = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_keyRepeatInterval, 0); - mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); } } @@ -1176,12 +1173,12 @@ private void startLongPressTimer(final Key key) { } private int getLongPressTimeout(final int code) { - if (code == KeyCode.SHIFT) { - return sParams.mLongPressShiftLockTimeout; - } final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; - if (mIsInSlidingKeyInput) { - // We use longer timeout for sliding finger input started from the modifier key. + if (code == KeyCode.SHIFT || code == KeyCode.SYMBOL_ALPHA) { + // We use slightly longer timeout for shift-lock and the numpad long-press. + return longpressTimeout * 3 / 2; + } else if (mIsInSlidingKeyInput) { + // We use longer timeout for sliding finger input started from a modifier key. return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; } return longpressTimeout; diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 35dd5d32e..e21e2fa3d 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -94,8 +94,6 @@ - - diff --git a/app/src/main/res/values/config-common.xml b/app/src/main/res/values/config-common.xml index ee30cee46..946d838a3 100644 --- a/app/src/main/res/values/config-common.xml +++ b/app/src/main/res/values/config-common.xml @@ -37,9 +37,6 @@ 3000 5 - - 1200 - 8.0dp Customize currencies - Set main and secondary currency symbols, separated with space + Set main and up to 6 secondary currency symbols, separated with space Set image for day or night mode? From 2f6bef478e51374f072c53a545b8e2f3ce7b6558 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 30 Jun 2024 11:27:45 +0200 Subject: [PATCH 65/92] add new key codes for a bunch of hw keyboard keys (no labels except for escape) also make sure they are handled together with meta-state (e.g. ctrl+arrow keys) fixes #237 --- .../keyboard_parser/floris/KeyCode.kt | 161 +++++++++++++----- .../keyboard_parser/floris/KeyLabel.kt | 1 + .../keyboard_parser/floris/TextKeyData.kt | 2 +- .../keyboard/latin/inputlogic/InputLogic.java | 60 ++----- 4 files changed, 137 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index 36c9229e5..1ab79c5db 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -137,8 +137,32 @@ object KeyCode { const val PAGE_UP = -10010 const val PAGE_DOWN = -10011 const val META = -10012 - const val META_LOCK = -10013 // to be consistent with the CTRL/ALT(/FN LOCK codes, not sure whether this will be used + const val META_LOCK = -10013 // to be consistent with the CTRL/ALT/FN LOCK codes, not sure whether this will be used const val TAB = -10014 + const val ESCAPE = -10017 + const val INSERT = -10018 + const val SLEEP = -10019 + const val MEDIA_PLAY = -10020 + const val MEDIA_PAUSE = -10021 + const val MEDIA_PLAY_PAUSE = -10022 + const val MEDIA_NEXT = -10023 + const val MEDIA_PREVIOUS = -10024 + const val VOL_UP = -10025 + const val VOL_DOWN = -10026 + const val MUTE = -10027 + const val F1 = -10028 + const val F2 = -10029 + const val F3 = -10030 + const val F4 = -10031 + const val F5 = -10032 + const val F6 = -10033 + const val F7 = -10034 + const val F8 = -10035 + const val F9 = -10036 + const val F10 = -10037 + const val F11 = -10038 + const val F12 = -10039 + const val BACK = -10040 /** to make sure a FlorisBoard code works when reading a JSON layout */ fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { @@ -151,7 +175,9 @@ object KeyCode { // heliboard only SYMBOL_ALPHA, START_ONE_HANDED_MODE, STOP_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SHIFT_ENTER, - ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, PAGE_UP, PAGE_DOWN, META, TAB + ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, PAGE_UP, PAGE_DOWN, META, TAB, + ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT, MEDIA_PREVIOUS, + VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK -> this // conversion @@ -162,46 +188,97 @@ object KeyCode { else -> throw IllegalStateException("key code $this not yet supported") } - // todo: add more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 - // maybe not toChar for conversion of some special keys? - /** convert a codePoint to a KeyEvent.KEYCODE_, fallback to KeyEvent.KEYCODE_UNKNOWN */ - fun Int.toKeyEventCode(): Int = when (this.toChar().uppercaseChar()) { - '0' -> KeyEvent.KEYCODE_0 - '1' -> KeyEvent.KEYCODE_1 - '2' -> KeyEvent.KEYCODE_2 - '3' -> KeyEvent.KEYCODE_3 - '4' -> KeyEvent.KEYCODE_4 - '5' -> KeyEvent.KEYCODE_5 - '6' -> KeyEvent.KEYCODE_6 - '7' -> KeyEvent.KEYCODE_7 - '8' -> KeyEvent.KEYCODE_8 - '9' -> KeyEvent.KEYCODE_9 - 'A' -> KeyEvent.KEYCODE_A - 'B' -> KeyEvent.KEYCODE_B - 'C' -> KeyEvent.KEYCODE_C - 'D' -> KeyEvent.KEYCODE_D - 'E' -> KeyEvent.KEYCODE_E - 'F' -> KeyEvent.KEYCODE_F - 'G' -> KeyEvent.KEYCODE_G - 'H' -> KeyEvent.KEYCODE_H - 'I' -> KeyEvent.KEYCODE_I - 'J' -> KeyEvent.KEYCODE_J - 'K' -> KeyEvent.KEYCODE_K - 'L' -> KeyEvent.KEYCODE_L - 'M' -> KeyEvent.KEYCODE_M - 'N' -> KeyEvent.KEYCODE_N - 'O' -> KeyEvent.KEYCODE_O - 'P' -> KeyEvent.KEYCODE_P - 'Q' -> KeyEvent.KEYCODE_Q - 'R' -> KeyEvent.KEYCODE_R - 'S' -> KeyEvent.KEYCODE_S - 'T' -> KeyEvent.KEYCODE_T - 'U' -> KeyEvent.KEYCODE_U - 'V' -> KeyEvent.KEYCODE_V - 'W' -> KeyEvent.KEYCODE_W - 'X' -> KeyEvent.KEYCODE_X - 'Y' -> KeyEvent.KEYCODE_Y - 'Z' -> KeyEvent.KEYCODE_Z + // todo: three are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 + /** convert a keyCode / codePoint to a KeyEvent.KEYCODE_, fallback to KeyEvent.KEYCODE_UNKNOWN */ + fun Int.toKeyEventCode(): Int = if (this > 0) + when (this.toChar().uppercaseChar()) { + '/' -> KeyEvent.KEYCODE_SLASH + '\\' -> KeyEvent.KEYCODE_BACKSLASH + ';' -> KeyEvent.KEYCODE_SEMICOLON + ',' -> KeyEvent.KEYCODE_COMMA + '.' -> KeyEvent.KEYCODE_PERIOD + '\'' -> KeyEvent.KEYCODE_APOSTROPHE + '`' -> KeyEvent.KEYCODE_GRAVE + '*' -> KeyEvent.KEYCODE_STAR + ']' -> KeyEvent.KEYCODE_RIGHT_BRACKET + '[' -> KeyEvent.KEYCODE_LEFT_BRACKET + '+' -> KeyEvent.KEYCODE_PLUS + '-' -> KeyEvent.KEYCODE_MINUS + '=' -> KeyEvent.KEYCODE_EQUALS + '\n' -> KeyEvent.KEYCODE_ENTER + '\t' -> KeyEvent.KEYCODE_TAB + '0' -> KeyEvent.KEYCODE_0 + '1' -> KeyEvent.KEYCODE_1 + '2' -> KeyEvent.KEYCODE_2 + '3' -> KeyEvent.KEYCODE_3 + '4' -> KeyEvent.KEYCODE_4 + '5' -> KeyEvent.KEYCODE_5 + '6' -> KeyEvent.KEYCODE_6 + '7' -> KeyEvent.KEYCODE_7 + '8' -> KeyEvent.KEYCODE_8 + '9' -> KeyEvent.KEYCODE_9 + 'A' -> KeyEvent.KEYCODE_A + 'B' -> KeyEvent.KEYCODE_B + 'C' -> KeyEvent.KEYCODE_C + 'D' -> KeyEvent.KEYCODE_D + 'E' -> KeyEvent.KEYCODE_E + 'F' -> KeyEvent.KEYCODE_F + 'G' -> KeyEvent.KEYCODE_G + 'H' -> KeyEvent.KEYCODE_H + 'I' -> KeyEvent.KEYCODE_I + 'J' -> KeyEvent.KEYCODE_J + 'K' -> KeyEvent.KEYCODE_K + 'L' -> KeyEvent.KEYCODE_L + 'M' -> KeyEvent.KEYCODE_M + 'N' -> KeyEvent.KEYCODE_N + 'O' -> KeyEvent.KEYCODE_O + 'P' -> KeyEvent.KEYCODE_P + 'Q' -> KeyEvent.KEYCODE_Q + 'R' -> KeyEvent.KEYCODE_R + 'S' -> KeyEvent.KEYCODE_S + 'T' -> KeyEvent.KEYCODE_T + 'U' -> KeyEvent.KEYCODE_U + 'V' -> KeyEvent.KEYCODE_V + 'W' -> KeyEvent.KEYCODE_W + 'X' -> KeyEvent.KEYCODE_X + 'Y' -> KeyEvent.KEYCODE_Y + 'Z' -> KeyEvent.KEYCODE_Z + else -> KeyEvent.KEYCODE_UNKNOWN + } + else when (this) { + ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP + ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT + ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN + ARROW_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT + MOVE_START_OF_LINE -> KeyEvent.KEYCODE_MOVE_HOME + MOVE_END_OF_LINE -> KeyEvent.KEYCODE_MOVE_END + TAB -> KeyEvent.KEYCODE_TAB + PAGE_UP -> KeyEvent.KEYCODE_PAGE_UP + PAGE_DOWN -> KeyEvent.KEYCODE_PAGE_DOWN + ESCAPE -> KeyEvent.KEYCODE_ESCAPE + INSERT -> KeyEvent.KEYCODE_INSERT + SLEEP -> KeyEvent.KEYCODE_SLEEP + MEDIA_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY + MEDIA_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE + MEDIA_PLAY_PAUSE -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + MEDIA_NEXT -> KeyEvent.KEYCODE_MEDIA_NEXT + MEDIA_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS + VOL_UP -> KeyEvent.KEYCODE_VOLUME_UP + VOL_DOWN -> KeyEvent.KEYCODE_VOLUME_DOWN + MUTE -> KeyEvent.KEYCODE_VOLUME_MUTE + BACK -> KeyEvent.KEYCODE_BACK + F1 -> KeyEvent.KEYCODE_F1 + F2 -> KeyEvent.KEYCODE_F2 + F3 -> KeyEvent.KEYCODE_F3 + F4 -> KeyEvent.KEYCODE_F4 + F5 -> KeyEvent.KEYCODE_F5 + F6 -> KeyEvent.KEYCODE_F6 + F7 -> KeyEvent.KEYCODE_F7 + F8 -> KeyEvent.KEYCODE_F8 + F9 -> KeyEvent.KEYCODE_F9 + F10 -> KeyEvent.KEYCODE_F10 + F11 -> KeyEvent.KEYCODE_F11 + F12 -> KeyEvent.KEYCODE_F12 else -> KeyEvent.KEYCODE_UNKNOWN } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt index 6d039aa46..221bb432a 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyLabel.kt @@ -30,6 +30,7 @@ object KeyLabel { const val FN = "fn" const val META = "meta" const val TAB = "tab" + const val ESCAPE = "esc" /** to make sure a FlorisBoard label works when reading a JSON layout */ // resulting special labels should be names of FunctionalKey enum, case insensitive diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 20de0f760..c5ec0ad69 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -488,7 +488,7 @@ sealed interface KeyData : AbstractKeyData { KeyLabel.CURRENCY3 -> params.mLocaleKeyboardInfos.currencyKey.second[2] KeyLabel.CURRENCY4 -> params.mLocaleKeyboardInfos.currencyKey.second[3] KeyLabel.CURRENCY5 -> params.mLocaleKeyboardInfos.currencyKey.second[4] - KeyLabel.CTRL, KeyLabel.ALT, KeyLabel.FN, KeyLabel.META -> label.uppercase(Locale.US) + KeyLabel.CTRL, KeyLabel.ALT, KeyLabel.FN, KeyLabel.META , KeyLabel.ESCAPE -> label.uppercase(Locale.US) KeyLabel.TAB -> "!icon/tab_key|" else -> { if (label in toolbarKeyStrings.values) { diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 9ed916614..2b5dd3177 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -749,65 +749,37 @@ private void handleFunctionalEvent(final Event event, final InputTransaction inp inputTransaction.setDidAffectContents(); } break; - case KeyCode.ARROW_LEFT: - sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT); - break; - case KeyCode.ARROW_RIGHT: - sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT); - break; - case KeyCode.ARROW_UP: - sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_UP); - break; - case KeyCode.ARROW_DOWN: - sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN); - break; case KeyCode.UNDO: sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON); break; case KeyCode.REDO: sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON); break; - case KeyCode.MOVE_START_OF_LINE: - sendDownUpKeyEvent(KeyEvent.KEYCODE_MOVE_HOME); - break; - case KeyCode.MOVE_END_OF_LINE: - sendDownUpKeyEvent(KeyEvent.KEYCODE_MOVE_END); - break; - case KeyCode.PAGE_UP: - sendDownUpKeyEvent(KeyEvent.KEYCODE_PAGE_UP); - break; - case KeyCode.PAGE_DOWN: - sendDownUpKeyEvent(KeyEvent.KEYCODE_PAGE_DOWN); - break; - case KeyCode.TAB: - sendDownUpKeyEvent(KeyEvent.KEYCODE_TAB); - break; case KeyCode.VOICE_INPUT: // switching to shortcut IME, shift state, keyboard,... is handled by LatinIME, // {@link KeyboardSwitcher#onEvent(Event)}, or {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. // We need to switch to the shortcut IME. This is handled by LatinIME since the // input logic has no business with IME switching. - case KeyCode.CAPS_LOCK: - case KeyCode.SYMBOL_ALPHA: - case KeyCode.ALPHA: - case KeyCode.SYMBOL: - case KeyCode.NUMPAD: - case KeyCode.EMOJI: - case KeyCode.START_ONE_HANDED_MODE: - case KeyCode.STOP_ONE_HANDED_MODE: - case KeyCode.SWITCH_ONE_HANDED_MODE: - case KeyCode.CTRL: - case KeyCode.ALT: - case KeyCode.FN: - case KeyCode.META: + case KeyCode.CAPS_LOCK, KeyCode.SYMBOL_ALPHA, KeyCode.ALPHA, KeyCode.SYMBOL, KeyCode.NUMPAD, KeyCode.EMOJI, + KeyCode.START_ONE_HANDED_MODE, KeyCode.STOP_ONE_HANDED_MODE, KeyCode.SWITCH_ONE_HANDED_MODE, + KeyCode.CTRL, KeyCode.ALT, KeyCode.FN, KeyCode.META: break; default: if (event.getMMetaState() != 0) { // need to convert codepoint to KeyEvent.KEYCODE_ - int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(event.getMCodePoint()); - sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); - } else - throw new RuntimeException("Unknown key code : " + event.getMKeyCode()); + final int codeToConvert = event.getMKeyCode() < 0 ? event.getMKeyCode() : event.getMCodePoint(); + int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(codeToConvert); + if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) + sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); + return; // never crash if user inputs sth we don't have a KeyEvent.KEYCODE for + } else if (event.getMKeyCode() < 0) { + int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(event.getMKeyCode()); + if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { + sendDownUpKeyEvent(keyEventCode); + return; + } + } + throw new RuntimeException("Unknown key code : " + event.getMKeyCode()); } } From 61f1534b71b48debfb4e7dbcfc8700a9f82fcbc5 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 30 Jun 2024 16:33:56 +0200 Subject: [PATCH 66/92] fix functional key layout for tablets --- app/src/main/assets/layouts/functional_keys_tablet.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/assets/layouts/functional_keys_tablet.json b/app/src/main/assets/layouts/functional_keys_tablet.json index 17966185d..5eb7fabc3 100644 --- a/app/src/main/assets/layouts/functional_keys_tablet.json +++ b/app/src/main/assets/layouts/functional_keys_tablet.json @@ -22,9 +22,9 @@ "email": { "label": "@", "groupId": 1, "type": "function" }, "uri": { "label": "/", "groupId": 1, "type": "function" } }, - { "label": "language_switch" }, - { "label": "emoji" }, - { "label": "numpad" }, + { "$": "keyboard_state_selector", "languageKeyEnabled": { "$": "keyboard_state_selector", "alphabet": { "label": "language_switch" }}}, + { "$": "keyboard_state_selector", "emojiKeyEnabled": { "$": "keyboard_state_selector", "alphabet": { "label": "emoji" }}}, + { "$": "keyboard_state_selector", "symbols": { "label": "numpad" }}, { "label": "space" }, { "label": "period" }, { "$": "variation_selector", From b26ac87ece4bed197d0ddcc2eaacd70ce269d53c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 30 Jun 2024 16:48:09 +0200 Subject: [PATCH 67/92] update translations and version --- app/build.gradle | 4 +- .../main/assets/dictionaries_in_dict_repo.csv | 3 +- app/src/main/res/values-be/strings.xml | 32 ++++++- app/src/main/res/values-bn/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 91 ++++++++++++++++++- app/src/main/res/values-eu/strings.xml | 6 ++ app/src/main/res/values-gl/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 10 +- app/src/main/res/values-ru/strings.xml | 14 +-- .../metadata/android/ar/changelogs/2003.txt | 11 +++ .../metadata/android/ar/full_description.txt | 52 +++++------ .../android/en-US/changelogs/2100.txt | 8 ++ .../android/es-ES/short_description.txt | 1 + fastlane/metadata/android/es-ES/title.txt | 1 + .../android/nl-NL/changelogs/2001.txt | 4 +- .../android/nl-NL/changelogs/2002.txt | 2 +- .../android/nl-NL/changelogs/2003.txt | 11 +++ .../android/nl-NL/full_description.txt | 44 ++++----- 18 files changed, 230 insertions(+), 70 deletions(-) create mode 100644 fastlane/metadata/android/ar/changelogs/2003.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/2100.txt create mode 100644 fastlane/metadata/android/es-ES/short_description.txt create mode 100644 fastlane/metadata/android/es-ES/title.txt create mode 100644 fastlane/metadata/android/nl-NL/changelogs/2003.txt diff --git a/app/build.gradle b/app/build.gradle index 15cb5648b..fb4fc61d1 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "helium314.keyboard" minSdkVersion 21 targetSdkVersion 34 - versionCode 2003 - versionName '2.0' + versionCode 2100 + versionName '2.1' ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } diff --git a/app/src/main/assets/dictionaries_in_dict_repo.csv b/app/src/main/assets/dictionaries_in_dict_repo.csv index e8865006c..45bd6b3da 100644 --- a/app/src/main/assets/dictionaries_in_dict_repo.csv +++ b/app/src/main/assets/dictionaries_in_dict_repo.csv @@ -2,6 +2,7 @@ main,ar, main,hy, main,as, main,bn, +main,eu, main,be, main,bg, main,ca, @@ -65,7 +66,6 @@ main,ur, main,af,exp main,ar,exp main,bn_BD,exp -main,eu,exp main,bn,exp main,bg,exp main,cs,exp @@ -80,6 +80,7 @@ main,he,exp main,id,exp main,it,exp main,kab,exp +main,kk,exp addon,ml_ZZ,exp main,pms,exp main,ru,exp diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index cc12cadc5..3915d2187 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -21,7 +21,7 @@ %s мс. "Стандартныя сістэмныя" "Падказваць імёны кантактаў" - "Выкарыстоўваць імёны са спісу кантактаў для прапаноў і выпраўл." + Падказваць выпраўленні на аснове імёнаў са спісу кантактаў "Персанальныя прапановы" "Падвойны iнтэрвал" "Падвойнае нацiсканне на клавішу прабелу ўстаўляе кропку з наступным прабелам" @@ -82,7 +82,7 @@ "Па змаўчанні" Вітаем ў %s з ўводам жэстамі - "Пачаць" + Пачаць працу "Далей" Наладка %s Ўключыць %s @@ -205,7 +205,7 @@ "Адрабіць" Паўтарыць "Запам. паведамл. і даныя, якія вы ўводзіце, для паляпшэння прапаноў" - "Пачаць" + ОК "Далей" "Назад" "Гатова" @@ -343,4 +343,30 @@ Вага: Пошук %s (Студэнцкая) + Заўсёды пачынаць неадкладна + Хаваць панэль інструментаў пры з\'яўленні прапаноў + Замацоўваць клавішы панэлі інструментаў пры працяглым націску + Паводзіны клавішы змены мовы + Заўсёды выкарыстоўваць сярэднюю прапанову + Час аднаўлення хуткага друку + Выбраць клавішы панэлі інструментаў буфера абмену + Гэтая настройка паказвае ўсе колеры, якія выкарыстоўваюцца ўнутры праграмы. Спіс колераў можа мяняцца. Колер па змаўчанні з\'яўляецца выпадковым, і імёны не будуць перакладзены. + Паказваць панэль інструментаў пры ўводзе ці пры выбары тэксту + Хаваць панэль інструментаў аўтаматычна + Кантэнт скапіяваны + Паказваць панэль інструментаў аўтаматычна + Пры націску кропкі або пунктуацыі будзе выкарыстана сярэдняя прапанова + Гэта адключыць астатнія функцыі клавіш панэлі інструментаў пры працяглым націску + Закрыць буфер абмену + %s (Пашыраная) + Эмодзі + Выбраць замацаваныя клавішы панэлі інструментаў + Панэль інструментаў + Мансійская + Мансійская (%s) + Паказаць больш колераў + Змяніць раскладку функцыянальных клавіш + Функцыянальныя клавішы + Функцыянальныя клавішы (Сімвалы) + Функцыянальныя клавішы (Дадатковыя сімвалы) \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index ff80f4329..99f866fd7 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -391,9 +391,11 @@ মানসি (%s) পিন করা নেই এমন অন্য টুলবার বোতামের দীর্ঘচাপের কার্যাবলি নিষ্ক্রিয় করবে অধিক রং প্রদর্শন - এই সেটিংস অভ্যন্তরীণভাবে ব্যবহৃত সকল রং প্রকাশ করে। রঙের তালিকা যেকোনো সময় পরিবর্তিত হতে পারে। কোনো ডিফল্ট রং নেই, এবং নাম অনুবাদ করা হবে না। + এই পছন্দসমূহ অভ্যন্তরীণভাবে ব্যবহৃত সকল রং প্রকাশ করে। রঙের তালিকা যেকোনো সময় পরিবর্তিত হতে পারে। সহজাত রঙ যেকোনোটি হতে পারে এবং নাম অনুবাদ করা হবে না। ফাংশনাল বোতাম লেআউট নিজস্বীকরণ ফাংশনাল বোতাম ফাংশনাল বোতাম (প্রতীক) ফাংশনাল বোতাম (অধিক প্রতীক) + দ্রুত লেখার বিরতিকাল + সর্বদা সঙ্গেসঙ্গে চালু করো \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 157059e6a..01b41e7c8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -148,7 +148,7 @@ Sin límite Habilitar el historial del portapapeles Tiempo a mantener guardado el historial - %smin. + %s min %s (Akkhor) Si está deshabilitada, la tecla del portapapeles pegará el contenido del portapapeles, si lo hay Espacio automático después del punto @@ -248,7 +248,7 @@ Cerrar Selecciona el idioma para abrir ajustes Cambiar idioma - Ignorar la petición de otras aplicaciones de desactivar las sugerencias + Ignorar la petición de otras aplicaciones de desactivar las sugerencias (puede causar inconvenientes) Más autocorrección Guardar o cargar desde archivo. Advertencia: la restauración sobrescribirá los datos existentes Necesitarás la biblioteca para \'%s\'. Las bibliotecas incompatibles pueden bloquearse al utilizar la escritura gestual. @@ -280,4 +280,91 @@ Símbolos Más símbolos Números + Esto deshabilita otras acciones de pulsación prolongada para teclas de la barra de herramientas que no están fijadas + Seleccionar teclas fijas de la barra de herramientas + Fijar tecla de la barra de herramientas con pulsación prolongada + Teclas funcionales + Apariencia + Ninguna + Contenido copiado + Mostrar barra de herramientas automaticamente + Comportamiento de la tecla para cambiar idioma + Mostrar variantes definidas en los idiomas de entrada (default) + Seleccionar teclas de la barra de herramientas + Distancia de la división + Cambiar ambos + Estrechar espacio entre teclas + Mantener pulsadas teclas de símbolos para cambiar al teclado numérico + Escala del relleno inferior + Texto de tecla + Acento + Usar siempre la sugerencia del medio + Al presionar espacio o punto, la sugerencia del medio será ingresada + Preferir números localizados sobre números latinos + Seleccionar origen del indicio + Símbolos (Árabe) + Teléfono + Símbolos del teléfono + Teclado numérico + Teclado numérico (paisaje) + ¿Configurar imagen para modo diurno o nocturno? + Día + Dirección variable de la barra de herramientas + Kaitag + Kaitag (%s) + Cerrar historial del portapapeles + Seleccionar teclas de la barra de herramientas del portapapeles + Mostar indicaciones funcionales + Mostar indicaciones si pulsar una tecla prolongadamente activa funcionalidad adicional + Invertir dirección cuando un subtipo de teclado Derecha-a-Izquierda está seleccionado + %s (Estudiante) + %s (Extendido) + ¿A qué idioma debe agregarse el diccionario \"%1$s\" para %2$s? + Definido por el usuario + Rosa + Arena + Violeta + Descripción de características ocultas + Mostrar características que pueden pasar desapercibidas + almacenamiento protegido del dispositivo + Emoji + Tocar para editar el layout + Personalizar la disposición de las teclas funcionales + Teclas funcionales (Símbolos) + Teclas funcionales (Más símbolos) + Noche + Texto de la barra espaciadora + Entrada gestual + Barra de herramientas + Gesto de deslizar horizontalmente barra espaciadora + Gesto de deslizar verticalmente la barra espaciadora + Mover cursor + Ocultar barra de herramientas automaticamente + Ocultar barra de herramientas cuando hay sugerencias disponibles + Cortar + Texto de la tira de sugerencias + %s (Bengalí) + %s (Sebeolsik 390) + %s (Sebeolsik Final) + Azúl grisáceo + Chocolate + Nuboso + Bosque + Índigo + Océano + Alfabeto (Bépo) + Mostrar la barra de herramientas si se ingresa o selecciona texto + Mansi + Mansi (%s) + Definido por el usuario (noche) + Esta configuración muestra todos los colores que se usan internamente. La lista de colores puede cambiar en cualquier momento. No hay color por defecto y los nombres no serán traducidos. + Toca para previsualizar + Seleccionar colores para el texto y fondos + Texto del indicio de tecla + Fondo de tecla + Fondo de tecla funcional + Fondo de la barra espaciadora + Mostrar más colores + "Sin un diccionario, solo obtendrás sugerencias para texto que hayas ingresado anteriormente.<br> +\n Puedes descargar diccionarios %1$s, o comprobar si un diccionario para \"%2$s\" puede descargarse directamente %3$s." \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 494806ad5..284472ea0 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -364,4 +364,10 @@ Kopiatu da edukia Hizkuntza aldatzeko teklaren portaera Ezkutatu tresna-barra iradokizunak erabilgarri daudenean + Hasi beti berehala + Mansi (%s) + Idazketa azkarra berrerabiltzeko denbora + %s (Extended) + Emojia + Mansi \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 15575549b..936624ea5 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -352,7 +352,7 @@ Mansi Mansi (%s) Mostrar máis cores - Este axuste expón todas as cores que se usan internamente. A lista de cores pode cambiar en calquera momento. Non hai cor por defecto, e os nomes non serán traduciddos. + Este axuste expón todas as cores que se usan internamente. A lista de cores pode cambiar en calquera momento. A cor por defecto é aleatoria, e os nomes non serán traduciddos. Teclas función Teclas función (Máis símbolos) Personalizar a disposición de teclas función diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b6c1f1753..c560c74b6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -314,7 +314,7 @@ Alleen hoofdkleuren weergeven Meer kleuren tonen Alle kleuren tonen - Deze instelling toont alle kleuren bloot die intern worden gebruikt. De lijst met kleuren kan op elk moment veranderen. Er is geen standaardkleur en de namen worden niet vertaald. + Deze instelling toont alle kleuren bloot die intern worden gebruikt. De lijst met kleuren kan op elk moment veranderen. Er is een willekeurige standaardkleur en de namen worden niet vertaald. Klik voor een voorbeeld Toetsenbord-achtergrond Toetstekst @@ -340,8 +340,8 @@ Een bibliotheek is nodig voor \'%s\'. Incompatibele bibliotheken kunnen crashen bij het typen van gebaren. \n \nWaarschuwing: het laden van externe code kan een veiligheidsrisico vormen. Gebruik alleen een bibliotheek van een vertrouwde bron. - "Zonder woordenboek krijg je alleen suggesties voor tekst die eerder is ingevoerd.<br> -\n Je kunt woordenboeken %1$s downloaden, of een woordenboek voor \"%2$s\" %3$s direct downloaden." + Zonder woordenboek krijg je alleen suggesties voor tekst die eerder is ingevoerd.<br> +\nJe kunt woordenboeken %1$s downloaden, of een woordenboek voor \"%2$s\" %3$s direct downloaden. Varianten gedefinieerd in toetsenbordtalen weergeven (standaard) Functionele hints weergeven Alfabet (Bépo) @@ -401,4 +401,8 @@ Toon de werkbalk bij invoer of selectie van tekst Gedrag taalkeuze-toets Emoji + Altijd direct starten + Afkoelperiode bij snel typen + Stel hoofd- en secundaire valutasymbolen in, gescheiden door spatie + Valuta\'s aanpassen \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9f8bca2ea..fd32b889c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -348,7 +348,7 @@ Вырезать Поведение клавиши смены языка Эмодзи - Выбрать закрепленные клавиши панели инструментов + Выбрать закреплённые клавиши панели инструментов Это отключит остальные функции клавиш панели инструментов при длительном нажатии Закреплять клавиши панели инструментов при длительном нажатии Контент скопирован @@ -358,14 +358,16 @@ При нажатии точки или пунктуации будет использовано среднее предложение Закрыть буфер обмена Выбрать клавиши панели инструментов буфера обмена - %s (Extended) + %s (Расширенная) Скрывать панель инструментов при появлении предложений Панель инструментов Показывать панель инструментов при вводе или при выборе текста Показать больше цветов - Эта настройка показывает все цвета, используемые внутри приложения. Список цветов может меняться. Цвета по умолчанию нету и названия не будут переводиться. - Функциональные клавишы (дополнительные символы) + Эта настройка показывает все цвета, используемые внутри приложения. Список цветов может меняться. Цвет по умолчанию является случайным, и имена не будут переведены. + Функциональные клавиши (Дополнительные символы) Изменить раскладку функциональных клавиш - Функциональные клавишы - Функциональные клавишы (символы) + Функциональные клавиши + Функциональные клавиши (Символы) + Всегда начинать немедленно + Время восстановления быстрой печати \ No newline at end of file diff --git a/fastlane/metadata/android/ar/changelogs/2003.txt b/fastlane/metadata/android/ar/changelogs/2003.txt new file mode 100644 index 000000000..57a418b70 --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/2003.txt @@ -0,0 +1,11 @@ +* تخطيط مفتاح الوظائف قابل للتخصيص +* ضبط الرموز قليلاً والمزيد من تخطيطات الرموز +* الدعم الأساسي لمفاتيح alt، ctrl، fn، meta +* توسيع شريط الأدوات (وظيفة الضغط المطول، التثبيت الاختياري للضغط لفترة طويلة، العرض/الإخفاء التلقائي، شريط أدوات الحافظة الأفضل، ...) +* إضافة مفتاح علامة التبويب +* إضافة مؤشر قفل الحروف الكبيرة +* إضافة تخطيطات لبعض اللغات +* إضافة مفاتيح شريط الأدوات كمفاتيح لوحة المفاتيح +* السماح بتخصيص كافة الألوان +* إشعار نخب عند نسخ النص +* إصلاحات الأخطاء والمزيد من التحسينات، راجع ملاحظات الإصدار الكاملة diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt index 5ce7ca16b..bbeb5e642 100644 --- a/fastlane/metadata/android/ar/full_description.txt +++ b/fastlane/metadata/android/ar/full_description.txt @@ -1,30 +1,30 @@ -HeliBoard عبارة عن لوحة مفاتيح مفتوحة المصدر تهتم بالخصوصية، وتعتمد على AOSP / OpenBoard. -لا يستخدم إذن الإنترنت، وبالتالي فهو غير متصل بالإنترنت بنسبة 100٪. +HeliBoard هو عبارة عن لوحة مفاتيح مفتوحة المصدر تهتم بالخصوصية، وتعتمد على AOSP / OpenBoard. +لا تستخدم إذن الإنترنت، وبالتالي فهو غير متصل بالإنترنت بنسبة 100%. المميزات:
      -
    • أضف قواميس للاقتراحات والتدقيق الإملائي
    • -
        -
      • قم ببناء قاموس خاص بك، أو احصل عليه هنا، أو في القسم التجريبي (قد تختلف الجودة)
      • -
      • يمكن استخدام قواميس إضافية للرموز التعبيرية أو الرموز العلمية لتقديم اقتراحات (على غرار "البحث عن الرموز التعبيرية")
      • -
      • لاحظ أنه بالنسبة للتخطيطات الكورية، تعمل الاقتراحات فقط باستخدام هذا القاموس، والأدوات الموجودة في مستودع القاموس غير قادرين على إنشاء قواميس صالحة للعمل
      • -
      -
    • تخصيص سمات لوحة المفاتيح (النمط والألوان وصورة الخلفية)
    • -
        -
      • يمكنه متابعة إعداد النهار/الليل للنظام على نظام التشغيل Android 10+ (وفي بعض إصدارات Android 9)
      • -
      • يمكنه متابعة الألوان الديناميكية لنظام التشغيل Android 12+
      • -
      -
    • تخصيص لوحة المفاتيح تخطيطات (متوفرة فقط عند تعطيل استخدام لغات النظام)
    • -
    • تخصيص تخطيطات خاصة، مثل الرموز أو الأرقام أو تخطيط المفتاح الوظيفي
    • -
    • الكتابة بلغات متعددة
    • -
    • الكتابة بالتمرير (فقط مع مكتبة المصادر المغلقة ☹️)
    • -
        -
      • المكتبة غير مضمنة في التطبيق، حيث لا تتوفر مكتبة متوافقة مفتوحة المصدر
      • -
      • يمكن استخراجه من حزم GApps ("swypelibs")، أو تنزيله هنا (انقر على الملف ثم "خام" أو زِر التنزيل الصغير )
      • -
      -
    • سجل الحافظة
    • -
    • وضع اليد الواحدة
    • -
    • تقسيم لوحة المفاتيح (متاح فقط إذا كانت الشاشة كبيرة بدرجة كافية)
    • -
    • لوحة الأرقام
    • -
    • النسخ الاحتياطي واستعادة الإعدادات الخاصة بك وبيانات الكلمات / التاريخ المستفادة
    • +
    • أضف قواميس للاقتراحات والتدقيق الإملائي
    • +
        +
      • قم ببناء قاموس خاص بك، أو احصل عليه هنا، أو في القسم التجريبي (قد تختلف الجودة)
      • +
      • يمكن استخدام قواميس إضافية للرموز التعبيرية أو الرموز العلمية لتقديم اقتراحات (على غرار "البحث عن الرموز التعبيرية")
      • +
      • لاحظ أنه بالنسبة للتخطيطات الكورية، تعمل الاقتراحات فقط باستخدام هذا القاموس، والأدوات الموجودة في مستودع القاموس غير قادرين على إنشاء قواميس صالحة للعمل
      • +
      +
    • تخصيص سمات لوحة المفاتيح (النمط والألوان وصورة الخلفية)
    • +
        +
      • يمكنه متابعة إعداد النهار/الليل للنظام على نظام التشغيل Android 10+ (وفي بعض إصدارات Android 9)
      • +
      • يمكنه متابعة الألوان الديناميكية لنظام التشغيل Android 12+
      • +
      +
    • تخصيص لوحة المفاتيح تخطيطات (متوفرة فقط عند تعطيل استخدام لغات النظام) +
    • تخصيص تخطيطات خاصة، مثل الرموز أو الأرقام أو تخطيط المفاتيح الوظيفية
    • +
    • الكتابة بلغات متعددة
    • +
    • الكتابة بالتمرير (فقط مع مكتبة المصادر المغلقة ☹️)
    • +
        +
      • المكتبة غير مضمنة في التطبيق، حيث لا تتوفر مكتبة متوافقة مفتوحة المصدر
      • +
      • يمكن استخراجه من حزم GApps ("swypelibs")، أو تنزيله هنا (انقر على الملف ثم "خام" أو زر التنزيل الصغير)
      • +
      +
    • سجل الحافظة
    • +
    • وضع اليد الواحدة
    • +
    • تقسيم لوحة المفاتيح (متاح فقط إذا كانت الشاشة كبيرة بدرجة كافية)
    • +
    • لوحة الأرقام
    • +
    • النسخ الاحتياطي واستعادة إعداداتك وبيانات الكلمات / السجل المستفادة
    diff --git a/fastlane/metadata/android/en-US/changelogs/2100.txt b/fastlane/metadata/android/en-US/changelogs/2100.txt new file mode 100644 index 000000000..1bcb32256 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/2100.txt @@ -0,0 +1,8 @@ +* fix broken functional key layout for tablets +* only show language switch key when there is something to switch to +* make default colors for "all colors" setting random instead of gray +* allow customizing start lag for gestures during typing, by @devycarol +* allow customizing currency keys +* reduce long-press time for shift -> caps lock, by @devycarol +* extend superscript popups in number row and symbols layout, by @b02860de585071a2 +* minor fixes and improvements diff --git a/fastlane/metadata/android/es-ES/short_description.txt b/fastlane/metadata/android/es-ES/short_description.txt new file mode 100644 index 000000000..74199465a --- /dev/null +++ b/fastlane/metadata/android/es-ES/short_description.txt @@ -0,0 +1 @@ +Teclado personalizable de código abierto diff --git a/fastlane/metadata/android/es-ES/title.txt b/fastlane/metadata/android/es-ES/title.txt new file mode 100644 index 000000000..e9841ace0 --- /dev/null +++ b/fastlane/metadata/android/es-ES/title.txt @@ -0,0 +1 @@ +HeliBoard diff --git a/fastlane/metadata/android/nl-NL/changelogs/2001.txt b/fastlane/metadata/android/nl-NL/changelogs/2001.txt index 21ab1ad06..d947afb94 100644 --- a/fastlane/metadata/android/nl-NL/changelogs/2001.txt +++ b/fastlane/metadata/android/nl-NL/changelogs/2001.txt @@ -7,5 +7,5 @@ * Optionele mogelijkheid van lang indrukken in de werkbalk * Werkbalkinstellingen in een aparte sectie * Tab-toets toegevoegd -* Voorziening voor Ctrl, werkbalk en andere belangrijke labels in lay-outs -* kleine fixes en verbeteringen +* Voorziening voor ctrl, werkbalk en andere belangrijke labels in lay-outs +* kleine oplossingen en verbeteringen diff --git a/fastlane/metadata/android/nl-NL/changelogs/2002.txt b/fastlane/metadata/android/nl-NL/changelogs/2002.txt index 23ebee70e..04632d94f 100644 --- a/fastlane/metadata/android/nl-NL/changelogs/2002.txt +++ b/fastlane/metadata/android/nl-NL/changelogs/2002.txt @@ -1,4 +1,4 @@ * Emoji-werkbalktoets toegevoegd, door @codokie (#845, #837) * verbeteringen met betrekking tot gedupliceerde letters (#225 en misschien anderen) * Vermijd het plaatsen van de cursor in emoji's (#859) -* Kleine correcties voor recent toegevoegde functies +* Kleine oplossingen voor recent toegevoegde functies diff --git a/fastlane/metadata/android/nl-NL/changelogs/2003.txt b/fastlane/metadata/android/nl-NL/changelogs/2003.txt new file mode 100644 index 000000000..30de35f08 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/2003.txt @@ -0,0 +1,11 @@ +* Aanpasbare lay-out van de functionele toetsen +* Symbolen en meer symboollay-outs enigszins aanpassen +* basisondersteuning voor alt, ctrl, fn, meta-toetsen +* uitbreiding werkbalk (functie voor lang indrukken, optioneel lang indrukken, automatisch weergeven/verbergen, verbeterde werkbalk voor klembord, ...) +* tab-toets toegevoegd +* caps lock-indicator toegevoegd +* lay-outs voor sommige talen toegevoegd +* werkbalk-toetsen als toetsenbord-toetsen +* alle kleuren aanpasbaar +* melding bij het kopiëren van tekst +* foutoplossingen en verdere verbeteringen, zie volledige release notes diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt index 62c2455cb..47d9f3dae 100644 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ b/fastlane/metadata/android/nl-NL/full_description.txt @@ -3,28 +3,28 @@ Het verlangt geen netwerkrechten en is dus 100% offline. Kenmerken:
      -
    • Woordenboeken toevoegen voor suggesties en spellingcontrole
    • +
    • Woordenboeken toevoegen voor suggesties en spellingcontrole
      • -
      • bouw je eigen, of download ze hier, of vanuit de sectie experimenteel (kwaliteit kan variëren)
      • -
      • aanvullende woordenboeken voor emoji's of wetenschappelijke symbolen kunnen worden gebruikt voor het geven van suggesties (vergelijkbaar met "emoji zoeken")
      • -
      • merk op dat voor Koreaanse lay-outs suggesties alleen werken met dit woordenboek , de hulpmiddelen in de woordenboek-opslagplaats kunnen geen functionele woordenboeken aanmaken
      • - -
      • Toetsenbordthema's aanpassen (stijl, kleuren en achtergrondafbeelding)
      • +
      • bouw je eigen, of download ze hier, of vanuit de sectie experimenteel (kwaliteit kan variëren)
      • +
      • aanvullende woordenboeken voor emoji's of wetenschappelijke symbolen kunnen worden gebruikt voor het geven van suggesties (vergelijkbaar met "emoji zoeken")
      • +
      • let op dat voor Koreaanse lay-outs suggesties alleen werken met dit woordenboek, de hulpmiddelen in de woordenboek-opslagplaats kunnen geen functionele woordenboeken aanmaken
      • +
      +
    • Toetsenbordthema's aanpassen (stijl, kleuren en achtergrondafbeelding)
      • -
      • Dag/nacht-instelling van het systeem volgen op Android 10+ (en op sommige versies van Android 9)
      • -
      • Dynamische kleuren volgen voor Android 12+
      • - -
      • Toetsenbord layouts aanpassen (alleen beschikbaar bij het uitschakelen van Systeemtalen gebruiken)
      • -
      • Aanpassing van speciale lay-outs, zoals symbolen, cijfers of functionele toetslay-outs
      • -
      • Meertalige invoer
      • -
      • Vegend typen (Glide typing) ( alleen m.b.v. gesloten bronbibliotheek ☹️)
      • +
      • Dag/nacht-instelling van het systeem volgen op Android 10+ (en sommige versies van Android 9)
      • +
      • Dynamische kleuren volgen voor Android 12+
      • +
      +
    • Toetsenbord layouts aanpassen (alleen beschikbaar bij het uitschakelen van Systeemtalen gebruiken)
    • +
    • Aanpassing van speciale lay-outs, zoals symbolen, cijfers of functionele toetslay-outs
    • +
    • Meertalige invoer
    • +
    • Vegend typen (Glide typing) ( alleen m.b.v. gesloten bronbibliotheek ☹️)
      • -
      • bibliotheek niet opgenomen in de app, omdat er geen compatibele open source bibliotheek beschikbaar is
      • -
      • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier downloaden (klik op het bestand en klik vervolgens op "raw" of de kleine download-knop)
      • - -
      • Klembordgeschiedenis
      • -
      • Bediening met één hand
      • -
      • Gesplitst toetsenbord (alleen beschikbaar als het scherm groot genoeg is)
      • -
      • Numpad
      • -
      • Back-up en herstel van instellingen en geleerde woorden / geschiedenisgegevens
      • - +
      • bibliotheek niet opgenomen in de app, omdat er geen compatibele open source bibliotheek beschikbaar is
      • +
      • kan worden ontleend uit GApps-pakketten (" swypelibs "), of hier downloaden (klik op het bestand en klik vervolgens op "raw" of de kleine download-knop)
      • +
      +
    • Klembordgeschiedenis
    • +
    • Bediening met één hand
    • +
    • Gesplitst toetsenbord (alleen beschikbaar als het scherm groot genoeg is)
    • +
    • Numpad
    • +
    • Back-up en herstel van instellingen en geleerde woorden / geschiedenisgegevens
    • +
    From 7dac19cec74ccf6f388725e614895003d493a0d3 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 30 Jun 2024 18:21:23 +0200 Subject: [PATCH 68/92] make all colors setting independent for day and night mode --- .../helium314/keyboard/keyboard/KeyboardTheme.kt | 14 +++++++------- app/src/main/java/helium314/keyboard/latin/App.kt | 11 +++++++++++ .../latin/settings/ColorsSettingsFragment.kt | 4 ++-- .../keyboard/latin/settings/Settings.java | 6 +++--- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt index 00fb9cdc8..c7f8b518f 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardTheme.kt @@ -19,7 +19,6 @@ import helium314.keyboard.latin.common.DynamicColors import helium314.keyboard.latin.common.readAllColorsMap import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.DeviceProtectedUtils -import helium314.keyboard.latin.utils.ResourceUtils class KeyboardTheme // Note: The themeId should be aligned with "themeId" attribute of Keyboard style in values/themes- diff --git a/app/src/main/res/values/keyboard-icons-lxx-light.xml b/app/src/main/res/values/keyboard-icons-lxx-light.xml index 237a3e864..86a5f12bd 100644 --- a/app/src/main/res/values/keyboard-icons-lxx-light.xml +++ b/app/src/main/res/values/keyboard-icons-lxx-light.xml @@ -48,11 +48,17 @@ @drawable/ic_dpad_right @drawable/ic_dpad_up @drawable/ic_dpad_down + @drawable/ic_word_left + @drawable/ic_word_right + @drawable/ic_page_up + @drawable/ic_page_down @drawable/ic_delete @drawable/ic_undo @drawable/ic_redo @drawable/ic_to_start @drawable/ic_to_end + @drawable/ic_page_start + @drawable/ic_page_end @drawable/ic_select @drawable/ic_close diff --git a/app/src/main/res/values/keyboard-icons-rounded.xml b/app/src/main/res/values/keyboard-icons-rounded.xml index dcf571c4f..fc0cadd28 100644 --- a/app/src/main/res/values/keyboard-icons-rounded.xml +++ b/app/src/main/res/values/keyboard-icons-rounded.xml @@ -47,11 +47,17 @@ @drawable/ic_dpad_right_rounded @drawable/ic_dpad_up_rounded @drawable/ic_dpad_down_rounded + @drawable/ic_word_left_rounded + @drawable/ic_word_right_rounded + @drawable/ic_page_up_rounded + @drawable/ic_page_down_rounded @drawable/ic_delete_rounded @drawable/ic_undo_rounded @drawable/ic_redo_rounded @drawable/ic_to_start_rounded @drawable/ic_to_end_rounded + @drawable/ic_page_start_rounded + @drawable/ic_page_end_rounded @drawable/ic_select_rounded @drawable/ic_close diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 416f63b6b..ce1454399 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -249,10 +249,16 @@ One-handed mode Full left Full right + Page start + Page end Left Right Up Down + Word left + Word right + Page up + Page down Undo Redo Close clipboard history @@ -788,8 +794,10 @@ New dictionary: ► Long-pressing pinned toolbar keys results in additional functionality: <br> \n\t• clipboard &#65515; paste <br> -\n\t• move left/right &#65515; move full left/right <br> +\n\t• move left/right &#65515; word left/right <br> \n\t• move up/down &#65515; page up/down <br> +\n\t• word left/right &#65515; line start/end <br> +\n\t• page up/down &#65515; page start/end <br> \n\t• copy &#65515; copy all <br> \n\t• select word &#65515; select all <br> \n\t• undo &#8596; redo <br> <br> From 2bce73ad7d0b424602e42f0b8789f81d1ff529a4 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 3 Jul 2024 22:44:19 +0200 Subject: [PATCH 73/92] fix build --- .../helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index 1df14f525..9c6ff2294 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -184,12 +184,12 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp if (inputLogic.moveCursorByAndReturnIfInsideComposingWord(moveSteps)) { // no need to finish input and restart suggestions if we're still in the word // this is a noticeable performance improvement - val newPosition: Int = inputLogic.mConnection.mExpectedSelStart + moveSteps + val newPosition = inputLogic.mConnection.expectedSelectionStart + moveSteps inputLogic.mConnection.setSelection(newPosition, newPosition) return true } inputLogic.finishInput() - val newPosition: Int = inputLogic.mConnection.mExpectedSelStart + moveSteps + val newPosition = inputLogic.mConnection.expectedSelectionStart + moveSteps inputLogic.mConnection.setSelection(newPosition, newPosition) inputLogic.restartSuggestionsOnWordTouchedByCursor(settings.current, keyboardSwitcher.currentKeyboardScript) return true From 21124a5a454639a5f688d2e455410dffb4e9756c Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Fri, 5 Jul 2024 14:42:09 -0600 Subject: [PATCH 74/92] Fix erroneous keyswipes (#919) --- .../keyboard/keyboard/PointerTracker.java | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java index e476352fa..3566a2e5b 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java @@ -131,7 +131,6 @@ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { // the popup keys panel currently being shown. equals null if no panel is active. private PopupKeysPanel mPopupKeysPanel; - private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3; // true if this pointer is in the dragging finger mode. boolean mIsInDraggingFinger; // true if this pointer is sliding from a modifier key and in the sliding key input mode, @@ -142,6 +141,9 @@ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { // true if dragging finger is allowed. private boolean mIsAllowedDraggingFinger; + // true if a keyswipe gesture is enabled and warranted. + private boolean mKeySwipeAllowed = false; + private static boolean sInKeySwipe = false; private final BatchInputArbiter mBatchInputArbiter; private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints; @@ -637,7 +639,7 @@ private void onDownEvent(final int x, final int y, final long eventTime, // A gesture should start only from a non-modifier key. Note that the gesture detection is // disabled when the key is repeating. mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() - && key != null && !key.isModifier(); + && key != null && !key.isModifier() && !mKeySwipeAllowed && !sInKeySwipe; if (mIsDetectingGesture) { mBatchInputArbiter.addDownEventPoint(x, y, eventTime, sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount()); @@ -666,6 +668,10 @@ private void onDownEventInternal(final int x, final int y, final long eventTime) mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger || (key != null && key.isModifier()) || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger(); + if (key != null && isSwiper(key.getCode()) && !sInGesture) { + mKeySwipeAllowed = true; + sInKeySwipe = true; + } mKeyboardLayoutHasBeenChanged = false; mIsTrackingForActionDisabled = false; resetKeySelectionByDraggingFinger(); @@ -704,9 +710,19 @@ private void resetKeySelectionByDraggingFinger() { sDrawingProxy.showSlidingKeyInputPreview(null); } + private boolean isSwiper(final int code) { + final SettingsValues sv = Settings.getInstance().getCurrent(); + return switch (code) { + case Constants.CODE_SPACE -> sv.mSpaceSwipeHorizontal != KeyboardActionListener.SWIPE_NO_ACTION + || sv.mSpaceSwipeVertical != KeyboardActionListener.SWIPE_NO_ACTION; + case KeyCode.DELETE -> sv.mDeleteSwipeEnabled; + default -> false; + }; + } + private void onGestureMoveEvent(final int x, final int y, final long eventTime, final boolean isMajorEvent, final Key key) { - if (!mIsDetectingGesture) { + if (!mIsDetectingGesture || sInKeySwipe) { return; } final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint( @@ -863,23 +879,24 @@ private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) } } - private void onMoveEventInternal(final int x, final int y, final long eventTime) { - final Key oldKey = mCurrentKey; + private void onKeySwipe(final int code, final int x, final int y, final long eventTime) { final SettingsValues sv = Settings.getInstance().getCurrent(); - - // todo (later): move key swipe stuff to a separate function (and finally extend it) - if (!mIsInSlidingKeyInput && oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) { - // reason for timeout: https://github.com/openboard-team/openboard/issues/411 - final int longpressTimeout = 2 * sv.mKeyLongpressTimeout / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; - if (mStartTime + longpressTimeout > System.currentTimeMillis()) - return; + final int fastTypingTimeout = 2 * sv.mKeyLongpressTimeout / 3; + // we don't want keyswipes to start immediately if the user is fast-typing, + // see https://github.com/openboard-team/openboard/issues/411 + if (System.currentTimeMillis() < mStartTime + fastTypingTimeout && sTypingTimeRecorder.isInFastTyping(eventTime)) + return; + if (code == Constants.CODE_SPACE) { int dX = x - mStartX; int dY = y - mStartY; - // vertical movement + // Vertical movement int stepsY = dY / sPointerStep; if (stepsY != 0 && abs(dX) < abs(dY) && !mInHorizontalSwipe) { - mInVerticalSwipe = true; + if (!mInVerticalSwipe) { + sTimerProxy.cancelKeyTimersOf(this); + mInVerticalSwipe = true; + } if (sListener.onVerticalSpaceSwipe(stepsY)) { mStartY += stepsY * sPointerStep; } @@ -889,23 +906,34 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime) // Horizontal movement int stepsX = dX / sPointerStep; if (stepsX != 0 && !mInVerticalSwipe) { - mInHorizontalSwipe = true; + if (!mInHorizontalSwipe) { + sTimerProxy.cancelKeyTimersOf(this); + mInHorizontalSwipe = true; + } if (sListener.onHorizontalSpaceSwipe(stepsX)) { mStartX += stepsX * sPointerStep; } } - return; - } - - if (!mIsInSlidingKeyInput && oldKey != null && oldKey.getCode() == KeyCode.DELETE && sv.mDeleteSwipeEnabled) { + } else if (code == KeyCode.DELETE) { // Delete slider int steps = (x - mStartX) / sPointerStep; - if (abs(steps) > 2 || (mInHorizontalSwipe && steps != 0)) { - sTimerProxy.cancelKeyTimersOf(this); - mInHorizontalSwipe = true; + if (steps != 0) { + if (!mInHorizontalSwipe) { + sTimerProxy.cancelKeyTimersOf(this); + mInHorizontalSwipe = true; + } mStartX += steps * sPointerStep; sListener.onMoveDeletePointer(steps); } + } + } + + private void onMoveEventInternal(final int x, final int y, final long eventTime) { + final Key oldKey = mCurrentKey; + + // todo (later): move key swipe stuff to KeyboardActionListener (and finally extend it) + if (mKeySwipeAllowed) { + onKeySwipe(oldKey.getCode(), x, y, eventTime); return; } @@ -985,7 +1013,7 @@ private void onUpEventInternal(final int x, final int y, final long eventTime) { // Release the last pressed key. setReleasedKeyGraphics(currentKey, true); - if(mInHorizontalSwipe && currentKey.getCode() == KeyCode.DELETE) { + if (mInHorizontalSwipe && currentKey.getCode() == KeyCode.DELETE) { sListener.onUpWithDeletePointerActive(); } @@ -1001,10 +1029,14 @@ private void onUpEventInternal(final int x, final int y, final long eventTime) { return; } - if (mInHorizontalSwipe || mInVerticalSwipe) { - mInHorizontalSwipe = false; - mInVerticalSwipe = false; - return; + if (mKeySwipeAllowed) { + mKeySwipeAllowed = false; + sInKeySwipe = false; + if (mInHorizontalSwipe || mInVerticalSwipe) { + mInHorizontalSwipe = false; + mInVerticalSwipe = false; + return; + } } if (sInGesture) { @@ -1049,9 +1081,6 @@ public void onLongPressed() { if (isShowingPopupKeysPanel()) { return; } - if(mInHorizontalSwipe || mInVerticalSwipe) { - return; - } final Key key = getKey(); if (key == null) { return; @@ -1093,6 +1122,10 @@ public void onLongPressed() { final int translatedY = popupKeysPanel.translateY(mLastY); popupKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); mPopupKeysPanel = popupKeysPanel; + if (mKeySwipeAllowed) { + mKeySwipeAllowed = false; + sInKeySwipe = false; + } } private void cancelKeyTracking() { @@ -1179,7 +1212,7 @@ private int getLongPressTimeout(final int code) { return longpressTimeout * 3 / 2; } else if (mIsInSlidingKeyInput) { // We use longer timeout for sliding finger input started from a modifier key. - return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; + return longpressTimeout * 3; } return longpressTimeout; } @@ -1218,6 +1251,10 @@ public void onKeyRepeat(final int code, final int repeatCount) { return; } mCurrentRepeatingKeyCode = code; + if (mKeySwipeAllowed) { + mKeySwipeAllowed = false; + sInKeySwipe = false; + } mIsDetectingGesture = false; final int nextRepeatCount = repeatCount + 1; startKeyRepeatTimer(nextRepeatCount); From bdab98c2c9c104067f7ae03504801c973260fbd9 Mon Sep 17 00:00:00 2001 From: codokie <151087174+codokie@users.noreply.github.com> Date: Sat, 6 Jul 2024 00:14:54 +0300 Subject: [PATCH 75/92] Clipboard suggestions (#647) --- .../compat/ClipboardManagerCompat.java | 8 ++ .../keyboard/latin/ClipboardHistoryManager.kt | 78 +++++++++++++++++++ .../helium314/keyboard/latin/LatinIME.java | 33 ++++++-- .../helium314/keyboard/latin/common/Colors.kt | 6 +- .../keyboard/latin/common/StringUtils.kt | 4 + .../keyboard/latin/inputlogic/InputLogic.java | 6 +- .../keyboard/latin/settings/Settings.java | 1 + .../latin/settings/SettingsValues.java | 8 ++ .../suggestions/SuggestionStripView.java | 13 ++-- .../keyboard/latin/utils/InputTypeUtils.java | 6 ++ .../clipboard_suggestion_background.xml | 20 +++++ .../main/res/layout/clipboard_suggestion.xml | 31 ++++++++ app/src/main/res/layout/suggestions_strip.xml | 1 + app/src/main/res/values/config-common.xml | 3 + .../values/strings-talkback-descriptions.xml | 2 + app/src/main/res/values/strings.xml | 4 + .../main/res/xml/prefs_screen_correction.xml | 7 ++ 17 files changed, 213 insertions(+), 18 deletions(-) create mode 100644 app/src/main/res/drawable/clipboard_suggestion_background.xml create mode 100644 app/src/main/res/layout/clipboard_suggestion.xml diff --git a/app/src/main/java/helium314/keyboard/compat/ClipboardManagerCompat.java b/app/src/main/java/helium314/keyboard/compat/ClipboardManagerCompat.java index 5d195f47b..649f2f65e 100644 --- a/app/src/main/java/helium314/keyboard/compat/ClipboardManagerCompat.java +++ b/app/src/main/java/helium314/keyboard/compat/ClipboardManagerCompat.java @@ -3,6 +3,7 @@ package helium314.keyboard.compat; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; import android.os.Build; @@ -29,4 +30,11 @@ public static Long getClipTimestamp(ClipData cd) { } } + public static Boolean getClipSensitivity(final ClipDescription cd) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return cd != null && cd.getExtras() != null && cd.getExtras().getBoolean("android.content.extra.IS_SENSITIVE"); + } + return null; // can't determine + } + } diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt index a26d900d8..8073666ff 100644 --- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt @@ -4,12 +4,24 @@ package helium314.keyboard.latin import android.content.ClipboardManager import android.content.Context +import android.text.InputType import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import androidx.core.view.isGone import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import helium314.keyboard.compat.ClipboardManagerCompat +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.common.ColorType +import helium314.keyboard.latin.common.isValidNumber +import helium314.keyboard.latin.databinding.ClipboardSuggestionBinding import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.DeviceProtectedUtils +import helium314.keyboard.latin.utils.InputTypeUtils +import helium314.keyboard.latin.utils.ToolbarKey import kotlin.collections.ArrayList class ClipboardHistoryManager( @@ -18,6 +30,7 @@ class ClipboardHistoryManager( private lateinit var clipboardManager: ClipboardManager private var onHistoryChangeListener: OnHistoryChangeListener? = null + private var clipboardSuggestionView: View? = null fun onCreate() { clipboardManager = latinIME.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -36,6 +49,7 @@ class ClipboardHistoryManager( // Make sure we read clipboard content only if history settings is set if (latinIME.mSettings.current?.mClipboardHistoryEnabled == true) { fetchPrimaryClip() + dontShowCurrentSuggestion = false } } @@ -90,6 +104,7 @@ class ClipboardHistoryManager( if (onHistoryChangeListener != null) { onHistoryChangeListener?.onClipboardHistoryEntriesRemoved(pos, count) } + removeClipboardSuggestion() } fun canRemove(index: Int) = historyEntries.getOrNull(index)?.isPinned != true @@ -131,6 +146,11 @@ class ClipboardHistoryManager( return clipData.getItemAt(0)?.coerceToText(latinIME) ?: "" } + private fun isClipSensitive(inputType: Int): Boolean { + ClipboardManagerCompat.getClipSensitivity(clipboardManager.primaryClip?.description)?.let { return it } + return InputTypeUtils.isPasswordInputType(inputType) + } + // pinned clips are stored in default shared preferences, not in device protected preferences! private fun loadPinnedClips() { val pinnedClipString = Settings.readPinnedClipString(latinIME) @@ -156,8 +176,66 @@ class ClipboardHistoryManager( fun onClipboardHistoryEntryMoved(from: Int, to: Int) } + fun getClipboardSuggestionView(editorInfo: EditorInfo?, parent: ViewGroup?): View? { + // maybe no need to create a new view + // but a cache has to consider a few possible changes, so better don't implement without need + clipboardSuggestionView = null + + // get the content, or return null + if (!latinIME.mSettings.current.mSuggestClipboardContent) return null + if (dontShowCurrentSuggestion) return null + if (parent == null) return null + val clipData = clipboardManager.primaryClip ?: return null + if (clipData.itemCount == 0 || clipData.description?.hasMimeType("text/*") == false) return null + val clipItem = clipData.getItemAt(0) ?: return null + val timeStamp = ClipboardManagerCompat.getClipTimestamp(clipData) ?: System.currentTimeMillis() + if (System.currentTimeMillis() - timeStamp > RECENT_TIME_MILLIS) return null + val content = clipItem.coerceToText(latinIME) + if (TextUtils.isEmpty(content)) return null + val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL + if (InputTypeUtils.isNumberInputType(inputType) && !content.isValidNumber()) return null + + // create the view + val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) + val textView = binding.clipboardSuggestionText + textView.text = if (isClipSensitive(inputType)) "*".repeat(content.length) else content + val clipIcon = latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.CLIPBOARD.name.lowercase()) + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(clipIcon, null, null, null) + textView.setOnClickListener { + dontShowCurrentSuggestion = true + latinIME.onTextInput(content.toString()) + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it); + binding.root.isGone = true + } + val closeButton = binding.clipboardSuggestionClose + closeButton.setImageDrawable(latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.CLOSE_HISTORY.name.lowercase())) + closeButton.setOnClickListener { removeClipboardSuggestion() } + + val colors = latinIME.mSettings.current.mColors + textView.setTextColor(colors.get(ColorType.KEY_TEXT)) + clipIcon?.let { colors.setColor(it, ColorType.KEY_ICON) } + colors.setColor(closeButton, ColorType.REMOVE_SUGGESTION_ICON) + colors.setBackground(binding.root, ColorType.CLIPBOARD_SUGGESTION_BACKGROUND) + + clipboardSuggestionView = binding.root + return clipboardSuggestionView + } + + private fun removeClipboardSuggestion() { + dontShowCurrentSuggestion = true + val csv = clipboardSuggestionView ?: return + if (csv.parent != null && !csv.isGone) { + // clipboard view is shown -> + latinIME.setNeutralSuggestionStrip() + latinIME.mHandler.postResumeSuggestions(false) + } + csv.isGone = true + } + companion object { // store pinned clips in companion object so they survive a keyboard switch (which destroys the current instance) private val historyEntries: MutableList = ArrayList() + private var dontShowCurrentSuggestion: Boolean = false + const val RECENT_TIME_MILLIS = 3 * 60 * 1000L // 3 minutes (for clipboard suggestions) } } diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 950f6f465..c188f56ea 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1029,12 +1029,11 @@ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restart // Space state must be updated before calling updateShiftState switcher.requestUpdatingShiftState(getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } - // This will set the punctuation suggestions if next word suggestion is off; - // otherwise it will clear the suggestion strip. + // Set neutral suggestions and show the toolbar if the "Auto show toolbar" setting is enabled. if (!mHandler.hasPendingResumeSuggestions()) { mHandler.cancelUpdateSuggestionStrip(); setNeutralSuggestionStrip(); - if (hasSuggestionStripView() && currentSettingsValues.mAutoShowToolbar) { + if (hasSuggestionStripView() && currentSettingsValues.mAutoShowToolbar && !tryShowClipboardSuggestion()) { mSuggestionStripView.setToolbarVisibility(true); } } @@ -1330,7 +1329,7 @@ public boolean onInlineSuggestionsResponse(InlineSuggestionsResponse response) { // Without this function the inline autofill suggestions will not be visible mHandler.cancelResumeSuggestions(); - mSuggestionStripView.setInlineSuggestionsView(inlineSuggestionView); + mSuggestionStripView.setExternalSuggestionView(inlineSuggestionView); return true; } @@ -1652,13 +1651,33 @@ public void pickSuggestionManually(final SuggestedWordInfo suggestionInfo) { updateStateAfterInputTransaction(completeInputTransaction); } - // This will show either an empty suggestion strip (if prediction is enabled) or - // punctuation suggestions (if it's disabled). - // The toolbar will be shown automatically if the relevant setting is enabled + /** + * Checks if a recent clipboard suggestion is available. If available, it is set in suggestion strip. + * returns whether a clipboard suggestion has been set. + */ + public boolean tryShowClipboardSuggestion() { + final View clipboardView = mClipboardHistoryManager.getClipboardSuggestionView(getCurrentInputEditorInfo(), mSuggestionStripView); + if (clipboardView != null && hasSuggestionStripView()) { + mSuggestionStripView.setExternalSuggestionView(clipboardView); + return true; + } + return false; + } + + // This will first try showing a clipboard suggestion. On success, the toolbar will be hidden + // if the "Auto hide toolbar" is enabled. Otherwise, an empty suggestion strip (if prediction + // is enabled) or punctuation suggestions (if it's disabled) will be set. + // Then, the toolbar will be shown automatically if the relevant setting is enabled // and there is a selection of text or it's the start of a line. @Override public void setNeutralSuggestionStrip() { final SettingsValues currentSettings = mSettings.getCurrent(); + if (tryShowClipboardSuggestion()) { + // clipboard suggestion has been set + if (hasSuggestionStripView() && currentSettings.mAutoHideToolbar) + mSuggestionStripView.setToolbarVisibility(false); + return; + } final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled ? SuggestedWords.getEmptyInstance() : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; diff --git a/app/src/main/java/helium314/keyboard/latin/common/Colors.kt b/app/src/main/java/helium314/keyboard/latin/common/Colors.kt index 80ea36b89..6876823c6 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/Colors.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/Colors.kt @@ -286,6 +286,7 @@ class DynamicColors(context: Context, override val themeStyle: String, override KEY_BACKGROUND -> keyBackground ACTION_KEY_POPUP_KEYS_BACKGROUND -> if (themeStyle == STYLE_HOLO) adjustedBackground else accent STRIP_BACKGROUND -> if (!hasKeyBorders && themeStyle == STYLE_MATERIAL) adjustedBackground else background + CLIPBOARD_SUGGESTION_BACKGROUND -> doubleAdjustedBackground NAVIGATION_BAR -> navBar MORE_SUGGESTIONS_HINT, SUGGESTED_WORD, SUGGESTION_TYPED_WORD, SUGGESTION_VALID_WORD -> adjustedKeyText ACTION_KEY_ICON, TOOL_BAR_EXPAND_KEY -> Color.WHITE @@ -467,7 +468,7 @@ class DefaultColors ( CLIPBOARD_PIN, SHIFT_KEY_ICON -> accent AUTOFILL_BACKGROUND_CHIP -> if (themeStyle == STYLE_MATERIAL && !hasKeyBorders) background else adjustedBackground GESTURE_PREVIEW, POPUP_KEYS_BACKGROUND, MORE_SUGGESTIONS_BACKGROUND, KEY_PREVIEW -> adjustedBackground - TOOL_BAR_EXPAND_KEY_BACKGROUND -> doubleAdjustedBackground + TOOL_BAR_EXPAND_KEY_BACKGROUND, CLIPBOARD_SUGGESTION_BACKGROUND -> doubleAdjustedBackground GESTURE_TRAIL -> gesture KEY_TEXT, REMOVE_SUGGESTION_ICON, FUNCTIONAL_KEY_TEXT, KEY_ICON -> keyText KEY_HINT_TEXT -> keyHintText @@ -519,7 +520,7 @@ class DefaultColors ( view.setBackgroundColor(Color.WHITE) // set white to make the color filters work when (color) { KEY_PREVIEW, POPUP_KEYS_BACKGROUND -> view.background.colorFilter = adjustedBackgroundFilter - FUNCTIONAL_KEY_BACKGROUND, KEY_BACKGROUND, MORE_SUGGESTIONS_WORD_BACKGROUND, SPACE_BAR_BACKGROUND, STRIP_BACKGROUND -> setColor(view.background, color) + FUNCTIONAL_KEY_BACKGROUND, KEY_BACKGROUND, MORE_SUGGESTIONS_WORD_BACKGROUND, SPACE_BAR_BACKGROUND, STRIP_BACKGROUND, CLIPBOARD_SUGGESTION_BACKGROUND -> setColor(view.background, color) ONE_HANDED_MODE_BUTTON -> setColor(view.background, if (keyboardBackground == null) MAIN_BACKGROUND else STRIP_BACKGROUND) MORE_SUGGESTIONS_BACKGROUND -> view.background.colorFilter = backgroundFilter MAIN_BACKGROUND -> { @@ -658,6 +659,7 @@ enum class ColorType { ONE_HANDED_MODE_BUTTON, REMOVE_SUGGESTION_ICON, STRIP_BACKGROUND, + CLIPBOARD_SUGGESTION_BACKGROUND, SUGGESTED_WORD, SUGGESTION_AUTO_CORRECT, SUGGESTION_TYPED_WORD, diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt index 8f3347129..69e8243db 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt @@ -103,6 +103,10 @@ fun String.splitOnFirstSpacesOnly(): List { return out } +fun CharSequence.isValidNumber(): Boolean { + return this.toString().toDoubleOrNull() != null +} + fun String.decapitalize(locale: Locale): String { if (isEmpty() || !this[0].isUpperCase()) return this diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 8a032b815..32b9a004d 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -1648,7 +1648,11 @@ public void performUpdateSuggestionStripSync(final SettingsValues settingsValues final SuggestedWords suggestedWords = holder.get(null, Constants.GET_SUGGESTED_WORDS_TIMEOUT); if (suggestedWords != null) { - mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords); + // Prefer clipboard suggestions (if available and setting is enabled) over beginning of sentence predictions. + if (!(suggestedWords.mInputStyle == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION + && mLatinIME.tryShowClipboardSuggestion())) { + mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords); + } } if (DebugFlags.DEBUG_ENABLED) { long runTimeMillis = System.currentTimeMillis() - startTimeMillis; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 8f9ce6e7f..fab604815 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -107,6 +107,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_AUTOSPACE_AFTER_PUNCTUATION = "autospace_after_punctuation"; public static final String PREF_ALWAYS_INCOGNITO_MODE = "always_incognito_mode"; public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; + public static final String PREF_SUGGEST_CLIPBOARD_CONTENT = "suggest_clipboard_content"; public static final String PREF_GESTURE_INPUT = "gesture_input"; public static final String PREF_VIBRATION_DURATION_SETTINGS = "vibration_duration_settings"; public static final String PREF_KEYPRESS_SOUND_VOLUME = "keypress_sound_volume"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index d93ce79cd..cde3e2a9a 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -125,6 +125,7 @@ public class SettingsValues { public final int mScoreLimitForAutocorrect; private final boolean mSuggestionsEnabledPerUserSettings; private final boolean mOverrideShowingSuggestions; + public final boolean mSuggestClipboardContent; public final SettingsValuesForSuggestion mSettingsValuesForSuggestion; public final boolean mIncognitoModeEnabled; public final boolean mLongPressSymbolsForNumpad; @@ -179,6 +180,7 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mScoreLimitForAutocorrect = (mAutoCorrectionThreshold < 0) ? 600000 // very aggressive : (mAutoCorrectionThreshold < 0.07 ? 800000 : 950000); // aggressive or modest mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); + mSuggestClipboardContent = readSuggestClipboardContent(prefs, res); mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout); mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration()); final float displayWidthDp = TypedValueCompat.pxToDp(res.getDisplayMetrics().widthPixels, res.getDisplayMetrics()); @@ -327,6 +329,12 @@ private static boolean readBigramPredictionEnabled(final SharedPreferences prefs R.bool.config_default_next_word_prediction)); } + private static boolean readSuggestClipboardContent (SharedPreferences prefs, + final Resources res) { + return prefs.getBoolean(Settings.PREF_SUGGEST_CLIPBOARD_CONTENT, res.getBoolean( + R.bool.config_default_suggest_clipboard_content)); + } + private static float readAutoCorrectionThreshold(final Resources res, final SharedPreferences prefs) { final String currentAutoCorrectionSetting = Settings.readAutoCorrectConfidence(prefs, res); diff --git a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java index 28f93c63e..6b668c51f 100644 --- a/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java +++ b/app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.java @@ -26,7 +26,6 @@ import android.util.TypedValue; import android.view.GestureDetector; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; @@ -59,7 +58,6 @@ import helium314.keyboard.latin.settings.SettingsValues; import helium314.keyboard.latin.suggestions.PopupSuggestionsView.MoreSuggestionsListener; import helium314.keyboard.latin.utils.DeviceProtectedUtils; -import helium314.keyboard.latin.utils.DialogUtilsKt; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.ToolbarKey; import helium314.keyboard.latin.utils.ToolbarUtilsKt; @@ -69,7 +67,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.widget.PopupMenu; public final class SuggestionStripView extends RelativeLayout implements OnClickListener, OnLongClickListener { @@ -110,7 +107,7 @@ public interface Listener { private final SuggestionStripLayoutHelper mLayoutHelper; private final StripVisibilityGroup mStripVisibilityGroup; - private boolean isInlineAutofillSuggestionsVisible = false; // Required to disable the more suggestions if inline autofill suggestions are visible + private boolean isExternalSuggestionVisible = false; // Required to disable the more suggestions if other suggestions are visible private static class StripVisibilityGroup { private final View mSuggestionStripView; @@ -258,7 +255,7 @@ private void updateKeys() { : km.isKeyguardLocked(); mToolbarExpandKey.setOnClickListener(hideToolbarKeys ? null : this); mPinnedKeys.setVisibility(hideToolbarKeys ? GONE : mSuggestionsStrip.getVisibility()); - isInlineAutofillSuggestionsVisible = false; + isExternalSuggestionVisible = false; } public void setRtl(final boolean isRtlLanguage) { @@ -281,9 +278,9 @@ public void setSuggestions(final SuggestedWords suggestedWords, final boolean is getContext(), mSuggestedWords, mSuggestionsStrip, this); } - public void setInlineSuggestionsView(final View view) { + public void setExternalSuggestionView(final View view) { clear(); - isInlineAutofillSuggestionsVisible = true; + isExternalSuggestionVisible = true; mSuggestionsStrip.addView(view); if (Settings.getInstance().getCurrent().mAutoHideToolbar) setToolbarVisibility(false); @@ -548,7 +545,7 @@ public boolean onScroll(@Nullable MotionEvent down, @NonNull MotionEvent me, flo public boolean onInterceptTouchEvent(final MotionEvent me) { // Disable More Suggestions if inline autofill suggestions is visible - if(isInlineAutofillSuggestionsVisible) { + if(isExternalSuggestionVisible) { return false; } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/InputTypeUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/InputTypeUtils.java index 8915457e0..f2a3c5eb3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/InputTypeUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/utils/InputTypeUtils.java @@ -18,6 +18,8 @@ public final class InputTypeUtils implements InputType { TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD; private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + private static final int TEXT_NUMBER_INPUT_TYPE = + TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL; private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = { InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS, InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, @@ -46,6 +48,10 @@ private static boolean isWebEmailAddressVariation(int variation) { return variation == TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; } + public static boolean isNumberInputType(final int inputType) { + return (inputType & TEXT_NUMBER_INPUT_TYPE) != 0; + } + public static boolean isEmailVariation(final int variation) { return variation == TYPE_TEXT_VARIATION_EMAIL_ADDRESS || isWebEmailAddressVariation(variation); diff --git a/app/src/main/res/drawable/clipboard_suggestion_background.xml b/app/src/main/res/drawable/clipboard_suggestion_background.xml new file mode 100644 index 000000000..f6cd9560c --- /dev/null +++ b/app/src/main/res/drawable/clipboard_suggestion_background.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/clipboard_suggestion.xml b/app/src/main/res/layout/clipboard_suggestion.xml new file mode 100644 index 000000000..42054ef09 --- /dev/null +++ b/app/src/main/res/layout/clipboard_suggestion.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/suggestions_strip.xml b/app/src/main/res/layout/suggestions_strip.xml index 6c0131240..ab2d27777 100644 --- a/app/src/main/res/layout/suggestions_strip.xml +++ b/app/src/main/res/layout/suggestions_strip.xml @@ -51,6 +51,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" + android:gravity="center_horizontal" android:hapticFeedbackEnabled="false" android:soundEffectsEnabled="false" /> 32.0dp 18% + + true + 9.6dp diff --git a/app/src/main/res/values/strings-talkback-descriptions.xml b/app/src/main/res/values/strings-talkback-descriptions.xml index dcfecae67..60a4d4fcb 100644 --- a/app/src/main/res/values/strings-talkback-descriptions.xml +++ b/app/src/main/res/values/strings-talkback-descriptions.xml @@ -20,6 +20,8 @@ No suggestion + + Clipboard suggestion Unknown character diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce1454399..301e554ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -134,6 +134,10 @@ Next-word suggestions Use the previous word in making suggestions + + Suggest clipboard content + + Show recently copied clipboard content as a suggestion Enable gesture typing diff --git a/app/src/main/res/xml/prefs_screen_correction.xml b/app/src/main/res/xml/prefs_screen_correction.xml index 0edb993cf..ab505cac9 100644 --- a/app/src/main/res/xml/prefs_screen_correction.xml +++ b/app/src/main/res/xml/prefs_screen_correction.xml @@ -108,6 +108,13 @@ android:defaultValue="@bool/config_center_suggestion_text_to_enter" android:persistent="true" /> + + Date: Sat, 6 Jul 2024 07:42:55 +0200 Subject: [PATCH 76/92] remove unused rm.txt --- app/src/main/assets/locale_key_texts/rm.txt | 2 -- tools/diacritics.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 app/src/main/assets/locale_key_texts/rm.txt mode change 100644 => 100755 tools/diacritics.py diff --git a/app/src/main/assets/locale_key_texts/rm.txt b/app/src/main/assets/locale_key_texts/rm.txt deleted file mode 100644 index 864fab38c..000000000 --- a/app/src/main/assets/locale_key_texts/rm.txt +++ /dev/null @@ -1,2 +0,0 @@ -[popup_keys] -o ò ó ö ô õ œ ø diff --git a/tools/diacritics.py b/tools/diacritics.py old mode 100644 new mode 100755 index d0e419f55..47744b237 --- a/tools/diacritics.py +++ b/tools/diacritics.py @@ -84,9 +84,9 @@ def read_diacritics() -> dict[str, list[str]]: with open(diacritics_file) as f: for line in f: if language == "": - language = line.strip() + language = line.split("#")[0].strip() else: - d[language] = list(map(str.strip, line.split(","))) + d[language] = list(map(str.strip, line.split("#")[0].split(","))) language = "" return d From 0bd76de9d92b396318224fcb4aaadc3759aa0d67 Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Sat, 6 Jul 2024 01:41:19 -0600 Subject: [PATCH 77/92] Add paste key and slightly re-arrange toolbar keys (#945) --- .../keyboard_parser/floris/KeyCode.kt | 4 +- .../keyboard/latin/ClipboardHistoryManager.kt | 4 +- .../keyboard/latin/utils/ToolbarUtils.kt | 55 ++++++++++--------- .../main/res/drawable/sym_keyboard_paste.xml | 13 +++++ .../drawable/sym_keyboard_paste_rounded.xml | 15 +++++ app/src/main/res/values/attrs.xml | 1 + .../main/res/values/keyboard-icons-holo.xml | 1 + .../res/values/keyboard-icons-lxx-light.xml | 1 + .../res/values/keyboard-icons-rounded.xml | 1 + app/src/main/res/values/strings.xml | 3 +- 10 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 app/src/main/res/drawable/sym_keyboard_paste.xml create mode 100644 app/src/main/res/drawable/sym_keyboard_paste_rounded.xml diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index 32621abfc..f4663899b 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -172,8 +172,8 @@ object KeyCode { fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { // working CURRENCY_SLOT_1, CURRENCY_SLOT_2, CURRENCY_SLOT_3, CURRENCY_SLOT_4, CURRENCY_SLOT_5, CURRENCY_SLOT_6, - VOICE_INPUT, LANGUAGE_SWITCH, SETTINGS, DELETE, ALPHA, SYMBOL, EMOJI, CLIPBOARD, CLIPBOARD_CUT, - UNDO, REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_SELECT_ALL, + VOICE_INPUT, LANGUAGE_SWITCH, SETTINGS, DELETE, ALPHA, SYMBOL, EMOJI, CLIPBOARD, CLIPBOARD_CUT, UNDO, + REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_PASTE, CLIPBOARD_SELECT_ALL, CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE, MOVE_START_OF_PAGE, MOVE_END_OF_PAGE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT, FN, CLIPBOARD_CLEAR_HISTORY, diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt index 8073666ff..d93a44eec 100644 --- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt @@ -199,12 +199,12 @@ class ClipboardHistoryManager( val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) val textView = binding.clipboardSuggestionText textView.text = if (isClipSensitive(inputType)) "*".repeat(content.length) else content - val clipIcon = latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.CLIPBOARD.name.lowercase()) + val clipIcon = latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.PASTE.name.lowercase()) textView.setCompoundDrawablesRelativeWithIntrinsicBounds(clipIcon, null, null, null) textView.setOnClickListener { dontShowCurrentSuggestion = true latinIME.onTextInput(content.toString()) - AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it); + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it) binding.root.isGone = true } val closeButton = binding.clipboardSuggestionClose diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index e5b511bb8..774455cd3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -36,12 +36,21 @@ fun createToolbarKey(context: Context, keyboardAttr: TypedArray, key: ToolbarKey fun getCodeForToolbarKey(key: ToolbarKey) = when (key) { VOICE -> KeyCode.VOICE_INPUT - SETTINGS -> KeyCode.SETTINGS CLIPBOARD -> KeyCode.CLIPBOARD + UNDO -> KeyCode.UNDO + REDO -> KeyCode.REDO + SETTINGS -> KeyCode.SETTINGS SELECT_ALL -> KeyCode.CLIPBOARD_SELECT_ALL + SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_WORD COPY -> KeyCode.CLIPBOARD_COPY CUT -> KeyCode.CLIPBOARD_CUT + PASTE -> KeyCode.CLIPBOARD_PASTE ONE_HANDED -> if (Settings.getInstance().current.mOneHandedModeEnabled) KeyCode.STOP_ONE_HANDED_MODE else KeyCode.START_ONE_HANDED_MODE + INCOGNITO -> KeyCode.TOGGLE_INCOGNITO_MODE + AUTOCORRECT -> KeyCode.TOGGLE_AUTOCORRECT + CLEAR_CLIPBOARD -> KeyCode.CLIPBOARD_CLEAR_HISTORY + CLOSE_HISTORY -> KeyCode.ALPHA + EMOJI -> KeyCode.EMOJI LEFT -> KeyCode.ARROW_LEFT RIGHT -> KeyCode.ARROW_RIGHT UP -> KeyCode.ARROW_UP @@ -50,21 +59,19 @@ fun getCodeForToolbarKey(key: ToolbarKey) = when (key) { WORD_RIGHT -> KeyCode.WORD_RIGHT PAGE_UP -> KeyCode.PAGE_UP PAGE_DOWN -> KeyCode.PAGE_DOWN - UNDO -> KeyCode.UNDO - REDO -> KeyCode.REDO - INCOGNITO -> KeyCode.TOGGLE_INCOGNITO_MODE - AUTOCORRECT -> KeyCode.TOGGLE_AUTOCORRECT FULL_LEFT -> KeyCode.MOVE_START_OF_LINE FULL_RIGHT -> KeyCode.MOVE_END_OF_LINE PAGE_START -> KeyCode.MOVE_START_OF_PAGE PAGE_END -> KeyCode.MOVE_END_OF_PAGE - SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_WORD - CLEAR_CLIPBOARD -> KeyCode.CLIPBOARD_CLEAR_HISTORY - CLOSE_HISTORY -> KeyCode.ALPHA - EMOJI -> KeyCode.EMOJI } fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) { + CLIPBOARD -> KeyCode.CLIPBOARD_PASTE + UNDO -> KeyCode.REDO + REDO -> KeyCode.UNDO + SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_ALL + COPY -> KeyCode.CLIPBOARD_COPY_ALL + PASTE -> KeyCode.CLIPBOARD LEFT -> KeyCode.WORD_LEFT RIGHT -> KeyCode.WORD_RIGHT UP -> KeyCode.PAGE_UP @@ -73,22 +80,26 @@ fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) { WORD_RIGHT -> KeyCode.MOVE_END_OF_LINE PAGE_UP -> KeyCode.MOVE_START_OF_PAGE PAGE_DOWN -> KeyCode.MOVE_END_OF_PAGE - UNDO -> KeyCode.REDO - REDO -> KeyCode.UNDO - COPY -> KeyCode.CLIPBOARD_COPY_ALL - SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_ALL - CLIPBOARD -> KeyCode.CLIPBOARD_PASTE else -> KeyCode.UNSPECIFIED } fun getStyleableIconId(key: ToolbarKey) = when (key) { VOICE -> R.styleable.Keyboard_iconShortcutKey - SETTINGS -> R.styleable.Keyboard_iconSettingsKey CLIPBOARD -> R.styleable.Keyboard_iconClipboardNormalKey + UNDO -> R.styleable.Keyboard_iconUndo + REDO -> R.styleable.Keyboard_iconRedo + SETTINGS -> R.styleable.Keyboard_iconSettingsKey SELECT_ALL -> R.styleable.Keyboard_iconSelectAll + SELECT_WORD -> R.styleable.Keyboard_iconSelectWord COPY -> R.styleable.Keyboard_iconCopyKey CUT -> R.styleable.Keyboard_iconCutKey + PASTE -> R.styleable.Keyboard_iconPasteKey ONE_HANDED -> R.styleable.Keyboard_iconStartOneHandedMode + INCOGNITO -> R.styleable.Keyboard_iconIncognitoKey + AUTOCORRECT -> R.styleable.Keyboard_iconAutoCorrect + CLEAR_CLIPBOARD -> R.styleable.Keyboard_iconClearClipboardKey + CLOSE_HISTORY -> R.styleable.Keyboard_iconClose + EMOJI -> R.styleable.Keyboard_iconEmojiNormalKey LEFT -> R.styleable.Keyboard_iconArrowLeft RIGHT -> R.styleable.Keyboard_iconArrowRight UP -> R.styleable.Keyboard_iconArrowUp @@ -97,18 +108,10 @@ fun getStyleableIconId(key: ToolbarKey) = when (key) { WORD_RIGHT -> R.styleable.Keyboard_iconWordRight PAGE_UP -> R.styleable.Keyboard_iconPageUp PAGE_DOWN -> R.styleable.Keyboard_iconPageDown - UNDO -> R.styleable.Keyboard_iconUndo - REDO -> R.styleable.Keyboard_iconRedo - INCOGNITO -> R.styleable.Keyboard_iconIncognitoKey - AUTOCORRECT -> R.styleable.Keyboard_iconAutoCorrect - CLEAR_CLIPBOARD -> R.styleable.Keyboard_iconClearClipboardKey FULL_LEFT -> R.styleable.Keyboard_iconFullLeft FULL_RIGHT -> R.styleable.Keyboard_iconFullRight PAGE_START -> R.styleable.Keyboard_iconPageStart PAGE_END -> R.styleable.Keyboard_iconPageEnd - SELECT_WORD -> R.styleable.Keyboard_iconSelectWord - CLOSE_HISTORY -> R.styleable.Keyboard_iconClose - EMOJI -> R.styleable.Keyboard_iconEmojiNormalKey } fun getToolbarIconByName(name: String, context: Context): Drawable? { @@ -122,7 +125,7 @@ fun getToolbarIconByName(name: String, context: Context): Drawable? { // names need to be aligned with resources strings (using lowercase of key.name) enum class ToolbarKey { - VOICE, CLIPBOARD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, CUT, ONE_HANDED, INCOGNITO, + VOICE, CLIPBOARD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, CUT, PASTE, ONE_HANDED, INCOGNITO, AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY, EMOJI, LEFT, RIGHT, UP, DOWN, WORD_LEFT, WORD_RIGHT, PAGE_UP, PAGE_DOWN, FULL_LEFT, FULL_RIGHT, PAGE_START, PAGE_END } @@ -130,7 +133,7 @@ enum class ToolbarKey { val toolbarKeyStrings = entries.associateWithTo(EnumMap(ToolbarKey::class.java)) { it.toString().lowercase(Locale.US) } val defaultToolbarPref by lazy { - val default = listOf(VOICE, CLIPBOARD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, LEFT, RIGHT) + val default = listOf(SETTINGS, VOICE, CLIPBOARD, UNDO, REDO, SELECT_WORD, COPY, PASTE, LEFT, RIGHT) val others = entries.filterNot { it in default || it == CLOSE_HISTORY } default.joinToString(";") { "${it.name},true" } + ";" + others.joinToString(";") { "${it.name},false" } } @@ -140,7 +143,7 @@ val defaultPinnedToolbarPref = entries.filterNot { it == CLOSE_HISTORY }.joinToS } val defaultClipboardToolbarPref by lazy { - val default = listOf(ONE_HANDED, UNDO, UP, DOWN, LEFT, RIGHT, CLEAR_CLIPBOARD, COPY, CUT, SELECT_WORD, CLOSE_HISTORY) + val default = listOf(CLEAR_CLIPBOARD, UP, DOWN, LEFT, RIGHT, UNDO, CUT, COPY, PASTE, SELECT_WORD, CLOSE_HISTORY) val others = entries.filterNot { it in default } default.joinToString(";") { "${it.name},true" } + ";" + others.joinToString(";") { "${it.name},false" } } diff --git a/app/src/main/res/drawable/sym_keyboard_paste.xml b/app/src/main/res/drawable/sym_keyboard_paste.xml new file mode 100644 index 000000000..13b990ade --- /dev/null +++ b/app/src/main/res/drawable/sym_keyboard_paste.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sym_keyboard_paste_rounded.xml b/app/src/main/res/drawable/sym_keyboard_paste_rounded.xml new file mode 100644 index 000000000..6e57a69b2 --- /dev/null +++ b/app/src/main/res/drawable/sym_keyboard_paste_rounded.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 5b4354f92..af55e3840 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -258,6 +258,7 @@ + diff --git a/app/src/main/res/values/keyboard-icons-holo.xml b/app/src/main/res/values/keyboard-icons-holo.xml index f7e2e33b5..e7dc6853b 100644 --- a/app/src/main/res/values/keyboard-icons-holo.xml +++ b/app/src/main/res/values/keyboard-icons-holo.xml @@ -31,6 +31,7 @@ @drawable/sym_keyboard_clipboard_holo @drawable/sym_keyboard_copy @drawable/sym_keyboard_cut + @drawable/sym_keyboard_paste @drawable/sym_keyboard_clear_clipboard_holo @drawable/sym_keyboard_start_onehanded_holo @drawable/sym_keyboard_stop_onehanded_holo diff --git a/app/src/main/res/values/keyboard-icons-lxx-light.xml b/app/src/main/res/values/keyboard-icons-lxx-light.xml index 86a5f12bd..5edb41210 100644 --- a/app/src/main/res/values/keyboard-icons-lxx-light.xml +++ b/app/src/main/res/values/keyboard-icons-lxx-light.xml @@ -36,6 +36,7 @@ @drawable/sym_keyboard_clipboard_lxx @drawable/sym_keyboard_copy @drawable/sym_keyboard_cut + @drawable/sym_keyboard_paste @drawable/sym_keyboard_clear_clipboard_lxx @drawable/sym_keyboard_start_onehanded_lxx @drawable/sym_keyboard_stop_onehanded_lxx diff --git a/app/src/main/res/values/keyboard-icons-rounded.xml b/app/src/main/res/values/keyboard-icons-rounded.xml index fc0cadd28..a11ecf7af 100644 --- a/app/src/main/res/values/keyboard-icons-rounded.xml +++ b/app/src/main/res/values/keyboard-icons-rounded.xml @@ -35,6 +35,7 @@ @drawable/sym_keyboard_clipboard_rounded @drawable/sym_keyboard_copy_rounded @drawable/sym_keyboard_cut + @drawable/sym_keyboard_paste_rounded @drawable/sym_keyboard_clear_clipboard_rounded @drawable/sym_keyboard_start_onehanded_rounded @drawable/sym_keyboard_stop_onehanded_rounded diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 301e554ff..d7c5a9f20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -242,8 +242,9 @@ Select toolbar keys + @android:string/paste @android:string/copy - Cut + @android:string/cut Clipboard Clear clipboard Voice input From 1bd585ab767070584947d94544fae2db9e124bac Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 18:55:42 +0200 Subject: [PATCH 78/92] update layout checking --- .../internal/keyboard_parser/floris/TextKeyData.kt | 2 +- .../keyboard/latin/utils/CustomLayoutUtils.kt | 13 ++++++++++--- layouts.md | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index c5ec0ad69..dc527ce54 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -337,7 +337,7 @@ sealed interface KeyData : AbstractKeyData { } override fun compute(params: KeyboardParams): KeyData? { - require(groupId <= GROUP_ENTER) { "only groups up to GROUP_ENTER are supported" } + require(groupId in 0..GROUP_ENTER) { "only positive groupIds up to GROUP_ENTER are supported" } require(label.isNotEmpty() || type == KeyType.PLACEHOLDER || code != KeyCode.UNSPECIFIED) { "non-placeholder key has no code and no label" } require(width >= 0f || width == -1f) { "illegal width $width" } val newLabel = label.convertFlorisLabel().resolveStringLabel(params) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt index e5ce2773c..c314865d7 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt @@ -20,6 +20,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToP import helium314.keyboard.latin.R import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.FileUtils +import kotlinx.serialization.SerializationException import java.io.File import java.io.IOException import java.math.BigInteger @@ -75,6 +76,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str .show() } +/** @return true if json, false if simple, null if invalid */ private fun checkLayout(layoutContent: String, context: Context): Boolean? { val params = KeyboardParams() params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET) @@ -85,18 +87,23 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? { if (!checkKeys(keys)) return null return true - } catch (e: Exception) { Log.w(TAG, "error parsing custom json layout", e) } + } catch (e: SerializationException) { + Log.w(TAG, "json parsing error", e) + } catch (e: Exception) { + Log.w(TAG, "json layout parsed, but considered invalid", e) + return null + } try { val keys = RawKeyboardParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } } if (!checkKeys(keys)) return null return false } catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) } - if (layoutContent.trimStart().startsWith("[")) { + if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]")) { // layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error try { RawKeyboardParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } } - } catch (e: Exception) { Log.w(TAG, "error parsing custom json layout", e) } + } catch (e: Exception) { Log.w(TAG, "json parsing error", e) } } return null } diff --git a/layouts.md b/layouts.md index e6576eb49..bad085614 100644 --- a/layouts.md +++ b/layouts.md @@ -58,7 +58,7 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm * `codePoints`: when multiple code points should be entered, only available for `multi_text_key` * `label`: text to display on the key, determined from code if empty * There are some special values, see the [label section](#labels) -* `groupId`: which additional popup keys to show, `0` is default and does not add anything, `1` adds the comma popup keys, and `2` adds the period popup keys +* `groupId`: which additional popup keys to show, `0` is default and does not add anything, `1` adds the comma popup keys, `2` adds the period popup keys, `3` adds the action key popup keys (looks awkward though) * `popup`: list of keys to add in the popup, e.g. `"label": ")", "popup": {"relevant": [{ "label": "." }]}` is a `)` key with a `.` popup * Note that in popup keys, properties are ignored with the exception of `$`, `code`, `codePoints`, and `label` * When specifying a _selector_ key class in a popup key, it will be evaluated correctly (e.g. for changing popups dependent on shift state) From 595e7ab680640c084faab6d860b149c729a9f049 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 19:09:29 +0200 Subject: [PATCH 79/92] qwerty fallback if keyboard parsing fails --- .../keyboard/keyboard/KeyboardSwitcher.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index aa334334a..f422eec79 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -36,9 +36,11 @@ import helium314.keyboard.latin.LatinIME; import helium314.keyboard.latin.R; import helium314.keyboard.latin.RichInputMethodManager; +import helium314.keyboard.latin.RichInputMethodSubtype; import helium314.keyboard.latin.WordComposer; import helium314.keyboard.latin.settings.Settings; import helium314.keyboard.latin.settings.SettingsValues; +import helium314.keyboard.latin.utils.AdditionalSubtypeUtils; import helium314.keyboard.latin.utils.CapsModeUtils; import helium314.keyboard.latin.utils.LanguageOnSpacebarUtils; import helium314.keyboard.latin.utils.Log; @@ -148,7 +150,23 @@ public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues setti try { mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState, oneHandedModeEnabled); } catch (KeyboardLayoutSetException e) { - Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); + Log.e(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); + try { + final InputMethodSubtype qwerty = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mRichImm.getCurrentSubtypeLocale(), "qwerty", true); + mKeyboardLayoutSet = builder.setKeyboardGeometry(keyboardWidth, keyboardHeight) + .setSubtype(new RichInputMethodSubtype(qwerty)) + .setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey) + .setNumberRowEnabled(settingsValues.mShowsNumberRow) + .setLanguageSwitchKeyEnabled(settingsValues.isLanguageSwitchKeyEnabled()) + .setEmojiKeyEnabled(settingsValues.mShowsEmojiKey) + .setSplitLayoutEnabled(settingsValues.mIsSplitKeyboardEnabled) + .setOneHandedModeEnabled(oneHandedModeEnabled) + .build(); + mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState, oneHandedModeEnabled); + showToast("error loading the keyboard, falling back to qwerty", false); + } catch (KeyboardLayoutSetException e2) { + Log.e(TAG, "even fallback to qwerty failed: " + e2.mKeyboardId, e2.getCause()); + } } } From e5b82b53c460174fd2a18bcae84280296f220f56 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 20:09:59 +0200 Subject: [PATCH 80/92] fix contacts access being asked when turning setting off instead of when turning on --- .../keyboard/latin/spellcheck/SpellCheckerSettingsFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/spellcheck/SpellCheckerSettingsFragment.java b/app/src/main/java/helium314/keyboard/latin/spellcheck/SpellCheckerSettingsFragment.java index d82057335..af966e140 100644 --- a/app/src/main/java/helium314/keyboard/latin/spellcheck/SpellCheckerSettingsFragment.java +++ b/app/src/main/java/helium314/keyboard/latin/spellcheck/SpellCheckerSettingsFragment.java @@ -41,7 +41,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (!TextUtils.equals(key, Settings.PREF_USE_CONTACTS) || sharedPreferences.getBoolean(key, false)) { + if (!TextUtils.equals(key, Settings.PREF_USE_CONTACTS) || !sharedPreferences.getBoolean(key, false)) { return; } From 9f37c5505cae39b7f4a565fb95334d9794d09505 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 20:23:21 +0200 Subject: [PATCH 81/92] avoid accessing uninitialized RichInputMethodManager, fixes #952 --- .../internal/keyboard_parser/floris/TextKeyData.kt | 2 +- .../helium314/keyboard/latin/RichInputMethodManager.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index dc527ce54..7a826b595 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -122,7 +122,7 @@ sealed interface KeyData : AbstractKeyData { keys.add("!icon/clipboard_normal_key|!code/key_clipboard") if (!params.mId.mEmojiKeyEnabled && !params.mId.isNumberLayout) keys.add("!icon/emoji_normal_key|!code/key_emoji") - if (!params.mId.mLanguageSwitchKeyEnabled && !params.mId.isNumberLayout && RichInputMethodManager.getInstance().canSwitchLanguage()) + if (!params.mId.mLanguageSwitchKeyEnabled && !params.mId.isNumberLayout && RichInputMethodManager.canSwitchLanguage()) keys.add("!icon/language_switch_key|!code/key_language_switch") if (!params.mId.mOneHandedModeEnabled) keys.add("!icon/start_onehanded_mode_key|!code/key_start_onehanded") diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java index def47a181..185233ffa 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java @@ -360,10 +360,11 @@ private void updateCurrentSubtype(final InputMethodSubtype subtype) { mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); } - public boolean canSwitchLanguage() { - if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherSubtypes && hasMultipleEnabledSubtypesInThisIme(false)) + public static boolean canSwitchLanguage() { + if (!isInitialized()) return false; + if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherSubtypes && getInstance().hasMultipleEnabledSubtypesInThisIme(false)) return true; - if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherImes && mImm.getEnabledInputMethodList().size() > 1) + if (Settings.getInstance().getCurrent().mLanguageSwitchKeyToOtherImes && getInstance().mImm.getEnabledInputMethodList().size() > 1) return true; return false; } From 1e3c76475049117106034029e60eb72a71d66417 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 20:35:33 +0200 Subject: [PATCH 82/92] add quotation marks for russian, fixes #912 --- app/src/main/assets/locale_key_texts/ru.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/locale_key_texts/ru.txt b/app/src/main/assets/locale_key_texts/ru.txt index 7c8edcf87..f6062fb5a 100644 --- a/app/src/main/assets/locale_key_texts/ru.txt +++ b/app/src/main/assets/locale_key_texts/ru.txt @@ -1,8 +1,8 @@ [popup_keys] е ё ь ъ -' ’ ‚ ‘ -" ” „ “ +' ’ ‚ ‘ › ‹ +" ” „ “ » « [labels] alphabet: АБВ From 309d7f299e63d5785702b57ce27c6601ba928410 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 7 Jul 2024 21:06:24 +0200 Subject: [PATCH 83/92] clarify no-new-dictionaries in some more places --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d862f0241..37df29570 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ tl;dr (you should still read the full list though): * re-use existing mechanisms / code * low performance impact * make it a draft if you still want to work on it -* don't do translation PRs +* no translations or dictionaries Further * When the PR contains "fixes" , the related issue will be linked and automatically closed if the PR is merged (also works for other words like "fix", "resolve", "resolves", "closes", ...) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63716dfb8..850002f3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,3 +33,6 @@ See make-emoji-keys tool [README](tools/make-emoji-keys/README.md). # Translations Translations can be added using [Weblate](https://translate.codeberg.org/projects/heliboard/). You will need an account to update translations and add languages. Add the language you want to translate to in Languages -> Manage translated languages in the top menu bar. Updating translations in a PR will not be accepted, as it may cause conflicts with Weblate translations. + +# Dictionaries +No new dictionaries will be added to this app. Please submit dictionaries and the wordlist to the [dictionaries repository](https://codeberg.org/Helium314/aosp-dictionaries) From 105d044aa8b03ab4c8ca4d13ea0e793dc9755f4d Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:26:23 -0600 Subject: [PATCH 84/92] Space swipe to toggle numpad layout, with side bonuses (#950) * make the numpad key toggle-based, allowing for use in its own layout. it also remembers the symbols-shift state. * add space swipe gesture to toggle the numpad * fix sliding key input for the numpad key bugs --- app/src/main/assets/layouts/numpad.json | 4 +- .../java/helium314/keyboard/keyboard/Key.java | 7 +- .../keyboard/KeyboardActionListener.java | 6 ++ .../keyboard/KeyboardActionListenerImpl.kt | 7 ++ .../keyboard/keyboard/KeyboardSwitcher.java | 9 ++ .../keyboard/keyboard/PointerTracker.java | 17 +++- .../keyboard/internal/KeyboardCodesSet.java | 2 - .../keyboard/internal/KeyboardIconsSet.kt | 2 - .../keyboard/internal/KeyboardState.java | 95 ++++++++++++++----- .../keyboard_parser/floris/TextKeyData.kt | 1 - .../keyboard/latin/settings/Settings.java | 2 + .../keyboard/latin/utils/ToolbarUtils.kt | 6 +- app/src/main/res/values/donottranslate.xml | 4 + app/src/main/res/values/strings.xml | 3 + layouts.md | 2 +- 15 files changed, 129 insertions(+), 38 deletions(-) diff --git a/app/src/main/assets/layouts/numpad.json b/app/src/main/assets/layouts/numpad.json index 4edfca2f0..d55793233 100644 --- a/app/src/main/assets/layouts/numpad.json +++ b/app/src/main/assets/layouts/numpad.json @@ -41,9 +41,9 @@ [ { "label": "alpha" }, { "label": "comma", "width": 0.1 }, - { "label": "symbol", "type": "character", "width": 0.12 }, + { "label": "symbol", "width": 0.12 }, { "label": "0", "type": "numeric" }, - { "label": "=", "width": 0.12, "popup": { "relevant": [ { "label": "≠"}, { "label": "≈"} ] } }, + { "label": "=", "type": "function", "width": 0.12, "popup": { "relevant": [ { "label": "≠"}, { "label": "≈"} ] } }, { "label": "period", "width": 0.1 }, { "label": "action" } ] diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 9cdeb36dc..419b50b0c 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -515,8 +515,11 @@ public final boolean isShift() { } public final boolean isModifier() { - return mCode == KeyCode.SHIFT || mCode == KeyCode.SYMBOL_ALPHA || mCode == KeyCode.ALPHA || mCode == KeyCode.SYMBOL - || mCode == KeyCode.CTRL || mCode == KeyCode.ALT || mCode == KeyCode.FN || mCode == KeyCode.META; + return switch (mCode) { + case KeyCode.SHIFT, KeyCode.SYMBOL_ALPHA, KeyCode.ALPHA, KeyCode.SYMBOL, KeyCode.NUMPAD, KeyCode.CTRL, + KeyCode.ALT, KeyCode.FN, KeyCode.META -> true; + default -> false; + }; } public final boolean isRepeatable() { diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java index 4320f7ba0..224337f34 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java @@ -97,6 +97,7 @@ public interface KeyboardActionListener { */ boolean onHorizontalSpaceSwipe(int steps); boolean onVerticalSpaceSwipe(int steps); + boolean toggleNumpad(boolean withSliding, boolean forceReturnToAlpha); void onMoveDeletePointer(int steps); void onUpWithDeletePointerActive(); @@ -107,6 +108,7 @@ public interface KeyboardActionListener { int SWIPE_NO_ACTION = 0; int SWIPE_MOVE_CURSOR = 1; int SWIPE_SWITCH_LANGUAGE = 2; + int SWIPE_TOGGLE_NUMPAD = 3; class Adapter implements KeyboardActionListener { @Override @@ -142,6 +144,10 @@ public boolean onVerticalSpaceSwipe(int steps) { return false; } @Override + public boolean toggleNumpad(boolean withSliding, boolean forceReturnToAlpha) { + return false; + } + @Override public void onMoveDeletePointer(int steps) {} @Override public void onUpWithDeletePointerActive() {} diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index 9c6ff2294..fad78ee8f 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -71,15 +71,22 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp override fun onHorizontalSpaceSwipe(steps: Int): Boolean = when (Settings.getInstance().current.mSpaceSwipeHorizontal) { KeyboardActionListener.SWIPE_MOVE_CURSOR -> onMoveCursorHorizontally(steps) KeyboardActionListener.SWIPE_SWITCH_LANGUAGE -> onLanguageSlide(steps) + KeyboardActionListener.SWIPE_TOGGLE_NUMPAD -> toggleNumpad(false, false) else -> false } override fun onVerticalSpaceSwipe(steps: Int): Boolean = when (Settings.getInstance().current.mSpaceSwipeVertical) { KeyboardActionListener.SWIPE_MOVE_CURSOR -> onMoveCursorVertically(steps) KeyboardActionListener.SWIPE_SWITCH_LANGUAGE -> onLanguageSlide(steps) + KeyboardActionListener.SWIPE_TOGGLE_NUMPAD -> toggleNumpad(false, false) else -> false } + override fun toggleNumpad(withSliding: Boolean, forceReturnToAlpha: Boolean): Boolean { + KeyboardSwitcher.getInstance().toggleNumpad(withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState, forceReturnToAlpha) + return true + } + override fun onMoveDeletePointer(steps: Int) { inputLogic.finishInput() val end = inputLogic.mConnection.expectedSelectionEnd diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index f422eec79..3b7449530 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -370,6 +370,15 @@ public void setNumpadKeyboard() { setKeyboard(KeyboardId.ELEMENT_NUMPAD, KeyboardSwitchState.OTHER); } + @Override + public void toggleNumpad(final boolean withSliding, final int autoCapsFlags, final int recapitalizeMode, + final boolean forceReturnToAlpha) { + if (DEBUG_ACTION) { + Log.d(TAG, "toggleNumpad"); + } + mState.toggleNumpad(withSliding, autoCapsFlags, recapitalizeMode, forceReturnToAlpha, true); + } + public enum KeyboardSwitchState { HIDDEN(-1), SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), diff --git a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java index 3566a2e5b..b04286c61 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java @@ -268,7 +268,8 @@ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, // primaryCode is different from {@link Key#mKeyCode}. private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, final int y, final long eventTime, final boolean isKeyRepeat) { - final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); + final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier() + && key.getCode() != KeyCode.NUMPAD; // we allow for the numpad to be toggled from sliding input final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState() && !isClearlyInsideKey(key, x, y); final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { @@ -879,6 +880,13 @@ private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) } } + private boolean oneShotSwipe(final int swipeSetting) { + return switch (swipeSetting) { + case KeyboardActionListener.SWIPE_NO_ACTION, KeyboardActionListener.SWIPE_TOGGLE_NUMPAD -> true; + default -> false; + }; + } + private void onKeySwipe(final int code, final int x, final int y, final long eventTime) { final SettingsValues sv = Settings.getInstance().getCurrent(); final int fastTypingTimeout = 2 * sv.mKeyLongpressTimeout / 3; @@ -896,7 +904,7 @@ private void onKeySwipe(final int code, final int x, final int y, final long eve if (!mInVerticalSwipe) { sTimerProxy.cancelKeyTimersOf(this); mInVerticalSwipe = true; - } + } else if (oneShotSwipe(sv.mSpaceSwipeVertical)) return; if (sListener.onVerticalSpaceSwipe(stepsY)) { mStartY += stepsY * sPointerStep; } @@ -909,7 +917,7 @@ private void onKeySwipe(final int code, final int x, final int y, final long eve if (!mInHorizontalSwipe) { sTimerProxy.cancelKeyTimersOf(this); mInHorizontalSwipe = true; - } + } else if (oneShotSwipe(sv.mSpaceSwipeHorizontal)) return; if (sListener.onHorizontalSpaceSwipe(stepsX)) { mStartX += stepsX * sPointerStep; } @@ -1105,7 +1113,8 @@ public void onLongPressed() { } } if (code == KeyCode.SYMBOL_ALPHA && Settings.getInstance().getCurrent().mLongPressSymbolsForNumpad) { - sListener.onCodeInput(KeyCode.NUMPAD, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false); + // toggle numpad with sliding input enabled, forcing return to the alpha layout when done + sListener.toggleNumpad(true, true); return; } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java index 4dc91203a..7c21f1542 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardCodesSet.java @@ -52,7 +52,6 @@ public static int getCode(final String name) { "key_emoji", "key_unspecified", "key_clipboard", - "key_numpad", "key_start_onehanded", "key_stop_onehanded", "key_switch_onehanded" @@ -78,7 +77,6 @@ public static int getCode(final String name) { KeyCode.EMOJI, KeyCode.NOT_SPECIFIED, KeyCode.CLIPBOARD, - KeyCode.NUMPAD, KeyCode.START_ONE_HANDED_MODE, KeyCode.STOP_ONE_HANDED_MODE, KeyCode.SWITCH_ONE_HANDED_MODE diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt index 0221085c9..8acb10e90 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt @@ -57,7 +57,6 @@ class KeyboardIconsSet { const val NAME_CLIPBOARD_NORMAL_KEY = "clipboard_normal_key" const val NAME_CLEAR_CLIPBOARD_KEY = "clear_clipboard_key" const val NAME_CUT_KEY = "cut_key" - const val NAME_NUMPAD_KEY = "numpad_key" const val NAME_START_ONEHANDED_KEY = "start_onehanded_mode_key" const val NAME_STOP_ONEHANDED_KEY = "stop_onehanded_mode_key" const val NAME_SWITCH_ONEHANDED_KEY = "switch_onehanded_key" @@ -90,7 +89,6 @@ class KeyboardIconsSet { NAME_CLIPBOARD_NORMAL_KEY to R.styleable.Keyboard_iconClipboardNormalKey, NAME_CLEAR_CLIPBOARD_KEY to R.styleable.Keyboard_iconClearClipboardKey, NAME_CUT_KEY to R.styleable.Keyboard_iconCutKey, - NAME_NUMPAD_KEY to R.styleable.Keyboard_iconNumpadKey, NAME_START_ONEHANDED_KEY to R.styleable.Keyboard_iconStartOneHandedMode, NAME_STOP_ONEHANDED_KEY to R.styleable.Keyboard_iconStopOneHandedMode, NAME_SWITCH_ONEHANDED_KEY to R.styleable.Keyboard_iconSwitchOneHandedMode, diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java index b74b809c0..dd13606bc 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java @@ -47,6 +47,8 @@ public interface SwitchActions { void setEmojiKeyboard(); void setClipboardKeyboard(); void setNumpadKeyboard(); + void toggleNumpad(final boolean withSliding, final int autoCapsFlags, final int recapitalizeMode, + final boolean forceReturnToAlpha); void setSymbolsKeyboard(); void setSymbolsShiftedKeyboard(); @@ -81,7 +83,8 @@ public interface SwitchActions { private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 4; private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 5; private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 6; - private static final int SWITCH_STATE_MOMENTARY_FROM_NUMPAD = 7; + private static final int SWITCH_STATE_MOMENTARY_TO_NUMPAD = 7; + private static final int SWITCH_STATE_MOMENTARY_FROM_NUMPAD = 8; private int mSwitchState = SWITCH_STATE_ALPHA; private static final int MODE_ALPHABET = 0; @@ -90,6 +93,7 @@ public interface SwitchActions { private static final int MODE_CLIPBOARD = 3; private static final int MODE_NUMPAD = 4; private int mMode = MODE_ALPHABET; + private int mModeBeforeNumpad = MODE_ALPHABET; private boolean mIsSymbolShifted; private boolean mPrevMainKeyboardWasShiftLocked; private boolean mPrevSymbolsKeyboardWasShifted; @@ -203,7 +207,8 @@ private void onRestoreKeyboardState(final int autoCapsFlags, final int recapital return; } if (state.mMode == MODE_NUMPAD) { - setNumpadKeyboard(); + // don't overwrite toggle state if reloading from orientation change, etc. + setNumpadKeyboard(false, false, false); return; } // Symbol mode @@ -379,17 +384,57 @@ private void setClipboardKeyboard() { mSwitchActions.setClipboardKeyboard(); } - private void setNumpadKeyboard() { + private void setNumpadKeyboard(final boolean withSliding, final boolean forceReturnToAlpha, + final boolean rememberState) { if (DEBUG_INTERNAL_ACTION) { Log.d(TAG, "setNumpadKeyboard"); } + if (rememberState) { + if (mMode == MODE_ALPHABET) { + // Remember caps lock mode and reset alphabet shift state. + mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); + mAlphabetShiftState.setShiftLocked(false); + } else if (mMode == MODE_SYMBOLS) { + // Remember symbols shifted state + mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; + } // When d-pad is added, "selection mode" may need to be remembered if not a global state + mModeBeforeNumpad = forceReturnToAlpha ? MODE_ALPHABET : mMode; + } mMode = MODE_NUMPAD; mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; - // Remember caps lock mode and reset alphabet shift state. - mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); - mAlphabetShiftState.setShiftLocked(false); mSwitchActions.setNumpadKeyboard(); - mSwitchState = SWITCH_STATE_NUMPAD; + mSwitchState = withSliding ? SWITCH_STATE_MOMENTARY_TO_NUMPAD : SWITCH_STATE_NUMPAD; + } + + public void toggleNumpad(final boolean withSliding, final int autoCapsFlags, final int recapitalizeMode, + final boolean forceReturnToAlpha, final boolean rememberState) { + if (DEBUG_INTERNAL_ACTION) { + Log.d(TAG, "toggleNumpad"); + } + if (mMode != MODE_NUMPAD) setNumpadKeyboard(withSliding, forceReturnToAlpha, rememberState); + else { + if (mModeBeforeNumpad == MODE_ALPHABET || forceReturnToAlpha) { + setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); + if (mPrevMainKeyboardWasShiftLocked) { + setShiftLocked(true); + } + mPrevMainKeyboardWasShiftLocked = false; + } else switch (mModeBeforeNumpad) { + case MODE_SYMBOLS -> { + if (mPrevSymbolsKeyboardWasShifted) { + setSymbolsShiftedKeyboard(); + } else { + setSymbolsKeyboard(); + } + mPrevSymbolsKeyboardWasShifted = false; + } + // toggling numpad and emoji layout isn't actually possible yet due to lack of toolbar + // keys or key-swipes in that layout, but included for safety. + case MODE_EMOJI -> setEmojiKeyboard(); + case MODE_CLIPBOARD -> setClipboardKeyboard(); + } + if (withSliding) mSwitchState = SWITCH_STATE_MOMENTARY_FROM_NUMPAD; + } } private void setOneHandedModeEnabled(boolean enabled) { @@ -430,6 +475,9 @@ public void onPressKey(final int code, final boolean isSinglePointer, final int } else if (code == KeyCode.ALPHA) { // don't start sliding, causes issues with fully customizable layouts // (also does not allow chording, but can be fixed later) + } else if (code == KeyCode.NUMPAD) { + // don't start sliding, causes issues with fully customizable layouts + // (also does not allow chording, but can be fixed later) } else { mShiftKeyState.onOtherKeyPressed(); mSymbolKeyState.onOtherKeyPressed(); @@ -459,17 +507,17 @@ public void onReleaseKey(final int code, final boolean withSliding, final int au + " sliding=" + withSliding + " " + stateToString(autoCapsFlags, recapitalizeMode)); } - if (code == KeyCode.SHIFT) { - onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode); - } else if (code == KeyCode.CAPS_LOCK) { - setShiftLocked(!mAlphabetShiftState.isShiftLocked()); - } else if (code == KeyCode.SYMBOL_ALPHA) { - onReleaseAlphaSymbol(withSliding, autoCapsFlags, recapitalizeMode); - } else if (code == KeyCode.SYMBOL) { - onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode); - } else if (code == KeyCode.ALPHA) { - onReleaseAlpha(withSliding, autoCapsFlags, recapitalizeMode); - } + switch (code) { + case KeyCode.SHIFT -> onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode); + case KeyCode.CAPS_LOCK -> setShiftLocked(!mAlphabetShiftState.isShiftLocked()); + case KeyCode.SYMBOL_ALPHA -> onReleaseAlphaSymbol(withSliding, autoCapsFlags, recapitalizeMode); + case KeyCode.SYMBOL -> onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode); + case KeyCode.ALPHA -> onReleaseAlpha(withSliding, autoCapsFlags, recapitalizeMode); + case KeyCode.NUMPAD -> { + // if no sliding, toggling is instead handled by {@link #onEvent} to accommodate toolbar key. + // also prevent sliding to clipboard layout, which isn't supported yet. + if (withSliding) setNumpadKeyboard(true, mModeBeforeNumpad == MODE_CLIPBOARD, true); + }} } private void onPressAlphaSymbol(final int autoCapsFlags, final int recapitalizeMode) { @@ -678,12 +726,14 @@ public void onFinishSlidingInput(final int autoCapsFlags, final int recapitalize if (DEBUG_EVENT) { Log.d(TAG, "onFinishSlidingInput: " + stateToString(autoCapsFlags, recapitalizeMode)); } - // Switch back to the previous keyboard mode if the user cancels sliding input. - switch (mSwitchState) { + // Switch back to the previous keyboard mode if the user didn't enter the numpad. + if (mMode != MODE_NUMPAD) switch (mSwitchState) { case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL -> toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE -> toggleShiftInSymbols(); case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT -> setAlphabetKeyboard(autoCapsFlags, recapitalizeMode); - case SWITCH_STATE_MOMENTARY_FROM_NUMPAD -> setNumpadKeyboard(); + case SWITCH_STATE_MOMENTARY_FROM_NUMPAD -> setNumpadKeyboard(false, false, false); + } else if (mSwitchState == SWITCH_STATE_MOMENTARY_TO_NUMPAD) { + toggleNumpad(false, autoCapsFlags, recapitalizeMode, false, false); } } @@ -763,7 +813,7 @@ public void onEvent(final Event event, final int autoCapsFlags, final int recapi setClipboardKeyboard(); } } else if (code == KeyCode.NUMPAD) { - setNumpadKeyboard(); + toggleNumpad(false, autoCapsFlags, recapitalizeMode, false, true); } else if (code == KeyCode.SYMBOL) { setSymbolsKeyboard(); } else if (code == KeyCode.START_ONE_HANDED_MODE) { @@ -793,6 +843,7 @@ private static String switchStateToString(final int switchState) { case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE -> "MOMENTARY-SYMBOL-MORE"; case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT -> "MOMENTARY-ALPHA_SHIFT"; case SWITCH_STATE_NUMPAD -> "NUMPAD"; + case SWITCH_STATE_MOMENTARY_TO_NUMPAD -> "MOMENTARY-TO-NUMPAD"; case SWITCH_STATE_MOMENTARY_FROM_NUMPAD -> "MOMENTARY-FROM-NUMPAD"; default -> null; }; diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt index 7a826b595..60d31ef0e 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/TextKeyData.kt @@ -480,7 +480,6 @@ sealed interface KeyData : AbstractKeyData { // todo (later): label and popupKeys for .com should be in localeKeyTexts, handled similar to currency key KeyLabel.COM -> ".com" KeyLabel.LANGUAGE_SWITCH -> "!icon/language_switch_key|!code/key_language_switch" - KeyLabel.NUMPAD -> "!icon/numpad_key|!code/key_numpad" KeyLabel.ZWNJ -> "!icon/zwnj_key|\u200C" KeyLabel.CURRENCY -> params.mLocaleKeyboardInfos.currencyKey.first KeyLabel.CURRENCY1 -> params.mLocaleKeyboardInfos.currencyKey.second[0] diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index fab604815..8059055c9 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -381,6 +381,7 @@ public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) { return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, "none")) { case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR; case "switch_language" -> KeyboardActionListener.SWIPE_SWITCH_LANGUAGE; + case "toggle_numpad" -> KeyboardActionListener.SWIPE_TOGGLE_NUMPAD; default -> KeyboardActionListener.SWIPE_NO_ACTION; }; } @@ -389,6 +390,7 @@ public static int readVerticalSpaceSwipe(final SharedPreferences prefs) { return switch (prefs.getString(PREF_SPACE_VERTICAL_SWIPE, "none")) { case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR; case "switch_language" -> KeyboardActionListener.SWIPE_SWITCH_LANGUAGE; + case "toggle_numpad" -> KeyboardActionListener.SWIPE_TOGGLE_NUMPAD; default -> KeyboardActionListener.SWIPE_NO_ACTION; }; } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index 774455cd3..5b48b8135 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -37,6 +37,7 @@ fun createToolbarKey(context: Context, keyboardAttr: TypedArray, key: ToolbarKey fun getCodeForToolbarKey(key: ToolbarKey) = when (key) { VOICE -> KeyCode.VOICE_INPUT CLIPBOARD -> KeyCode.CLIPBOARD + NUMPAD -> KeyCode.NUMPAD UNDO -> KeyCode.UNDO REDO -> KeyCode.REDO SETTINGS -> KeyCode.SETTINGS @@ -86,6 +87,7 @@ fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) { fun getStyleableIconId(key: ToolbarKey) = when (key) { VOICE -> R.styleable.Keyboard_iconShortcutKey CLIPBOARD -> R.styleable.Keyboard_iconClipboardNormalKey + NUMPAD -> R.styleable.Keyboard_iconNumpadKey UNDO -> R.styleable.Keyboard_iconUndo REDO -> R.styleable.Keyboard_iconRedo SETTINGS -> R.styleable.Keyboard_iconSettingsKey @@ -125,8 +127,8 @@ fun getToolbarIconByName(name: String, context: Context): Drawable? { // names need to be aligned with resources strings (using lowercase of key.name) enum class ToolbarKey { - VOICE, CLIPBOARD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, CUT, PASTE, ONE_HANDED, INCOGNITO, - AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY, EMOJI, LEFT, RIGHT, UP, DOWN, WORD_LEFT, WORD_RIGHT, + VOICE, CLIPBOARD, NUMPAD, UNDO, REDO, SETTINGS, SELECT_ALL, SELECT_WORD, COPY, CUT, PASTE, ONE_HANDED, + INCOGNITO, AUTOCORRECT, CLEAR_CLIPBOARD, CLOSE_HISTORY, EMOJI, LEFT, RIGHT, UP, DOWN, WORD_LEFT, WORD_RIGHT, PAGE_UP, PAGE_DOWN, FULL_LEFT, FULL_RIGHT, PAGE_START, PAGE_END } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 69d2ab0f2..a5df734f3 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -111,21 +111,25 @@ move_cursor switch_language + toggle_numpad none @string/space_swipe_move_cursor_entry @string/switch_language + @string/space_swipe_toggle_numpad_entry @string/action_none move_cursor switch_language + toggle_numpad none @string/space_swipe_move_cursor_entry @string/switch_language + @string/space_swipe_toggle_numpad_entry @string/action_none diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7c5a9f20..9cff4bdf7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -248,6 +248,7 @@ Clipboard Clear clipboard Voice input + @string/layout_numpad Settings @android:string/selectAll Select word @@ -858,6 +859,8 @@ New dictionary: None Move Cursor + + Toggle Numpad Variable toolbar direction diff --git a/layouts.md b/layouts.md index bad085614..60c9c90d4 100644 --- a/layouts.md +++ b/layouts.md @@ -83,7 +83,7 @@ Usually the label is what is displayed on the key. However, there are some speci * _alpha_: switch to alphabet keyboard (or main phone keyboard in case of phone layout) * _symbol_: switch to symbol keyboard (or phone symbols keyboard in case of phone layout) * _symbol_alpha_: toggle alpha / symbol keyboard - * _numpad_: switch to numpad layout + * _numpad_: toggle numpad layout * _emoji_: switch to emoji view * _com_: display common TLDs (.com and similar, currently not localized) * _language_switch_: language switch key From a3bc3b56ff7038c4d2ff6d6563b568b0df990a4d Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 10 Jul 2024 16:32:18 +0200 Subject: [PATCH 85/92] fix clipboard suggestion being shown for a split second when starting gesture typing --- .../java/helium314/keyboard/latin/LatinIME.java | 6 +++++- .../helium314/keyboard/latin/SuggestedWords.java | 15 ++++++++++++--- .../keyboard/latin/inputlogic/InputLogic.java | 3 +-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index c188f56ea..aecacd5f6 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1630,7 +1630,11 @@ public void getSuggestedWords(final int inputStyle, final int sequenceNumber, @Override public void showSuggestionStrip(final SuggestedWords suggestedWords) { if (suggestedWords.isEmpty()) { - setNeutralSuggestionStrip(); + // avoids showing clipboard suggestion when starting gesture typing + // should be fine, as there will be another suggestion in a few ms + // (but not a great style to avoid this visual glitch, maybe revert this commit and replace with sth better) + if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_UPDATE_BATCH) + setNeutralSuggestionStrip(); } else { setSuggestedWords(suggestedWords); } diff --git a/app/src/main/java/helium314/keyboard/latin/SuggestedWords.java b/app/src/main/java/helium314/keyboard/latin/SuggestedWords.java index ae99601e6..a6eae0984 100644 --- a/app/src/main/java/helium314/keyboard/latin/SuggestedWords.java +++ b/app/src/main/java/helium314/keyboard/latin/SuggestedWords.java @@ -39,9 +39,13 @@ public class SuggestedWords { private static final ArrayList EMPTY_WORD_INFO_LIST = new ArrayList<>(0); @NonNull private static final SuggestedWords EMPTY = new SuggestedWords( - EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, null /* typedWord */, - false /* typedWordValid */, false /* willAutoCorrect */, - false /* isObsoleteSuggestions */, INPUT_STYLE_NONE, NOT_A_SEQUENCE_NUMBER); + EMPTY_WORD_INFO_LIST, null, null, false, + false, false, INPUT_STYLE_NONE, NOT_A_SEQUENCE_NUMBER); + + @NonNull + private static final SuggestedWords EMPTY_BATCH = new SuggestedWords( + EMPTY_WORD_INFO_LIST, null, null, false, + false, false, INPUT_STYLE_UPDATE_BATCH, NOT_A_SEQUENCE_NUMBER); @Nullable public final SuggestedWordInfo mTypedWordInfo; @@ -195,6 +199,11 @@ public static SuggestedWords getEmptyInstance() { return SuggestedWords.EMPTY; } + @NonNull + public static SuggestedWords getEmptyBatchInstance() { + return SuggestedWords.EMPTY_BATCH; + } + // Should get rid of the first one (what the user typed previously) from suggestions // and replace it with what the user currently typed. public static ArrayList getTypedWordAndPreviousSuggestions( diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 32b9a004d..927f744e5 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -511,8 +511,7 @@ public void onStartBatchInput(final SettingsValues settingsValues, final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) { mWordBeingCorrectedByCursor = null; mInputLogicHandler.onStartBatchInput(); - handler.showGesturePreviewAndSuggestionStrip( - SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */); + handler.showGesturePreviewAndSuggestionStrip(SuggestedWords.getEmptyBatchInstance(), false); handler.cancelUpdateSuggestionStrip(); ++mAutoCommitSequenceNumber; mConnection.beginBatchEdit(); From bee51de8a59ec323df5a6d096dbf6961f0d814ad Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 10 Jul 2024 17:23:10 +0200 Subject: [PATCH 86/92] add setting when to switch to main keyboard view fixes #546 --- .../clipboard/ClipboardHistoryView.kt | 2 ++ .../keyboard/emoji/EmojiPalettesView.java | 2 ++ .../keyboard/internal/KeyboardState.java | 8 +---- .../settings/AdvancedSettingsFragment.kt | 30 +++++++++++++++++++ .../keyboard/latin/settings/Settings.java | 3 ++ .../latin/settings/SettingsValues.java | 6 ++++ app/src/main/res/values/strings.xml | 8 +++++ .../main/res/xml/prefs_screen_advanced.xml | 5 ++++ 8 files changed, 57 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt index acb7f76ba..3f178d206 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt @@ -268,6 +268,8 @@ class ClipboardHistoryView @JvmOverloads constructor( val clipContent = clipboardHistoryManager?.getHistoryEntryContent(clipId) keyboardActionListener?.onTextInput(clipContent?.content.toString()) keyboardActionListener?.onReleaseKey(KeyCode.NOT_SPECIFIED, false) + if (Settings.getInstance().current.mAlphaAfterClipHistoryEntry) + keyboardActionListener?.onCodeInput(KeyCode.ALPHA, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false) } override fun onClipboardHistoryEntryAdded(at: Int) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesView.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesView.java index a29412b9e..7fdcdb07a 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesView.java @@ -340,6 +340,8 @@ public void onReleaseKey(final Key key) { mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE, false); } mKeyboardActionListener.onReleaseKey(code, false); + if (Settings.getInstance().getCurrent().mAlphaAfterEmojiInEmojiView) + mKeyboardActionListener.onCodeInput(KeyCode.ALPHA, NOT_A_COORDINATE, NOT_A_COORDINATE, false); } public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java index dd13606bc..e6c1df61d 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardState.java @@ -782,17 +782,11 @@ public void onEvent(final Event event, final int autoCapsFlags, final int recapi || code == KeyCode.MULTIPLE_CODE_POINTS)) { mSwitchState = SWITCH_STATE_SYMBOL; } - // Switch back to alpha keyboard mode if user types one or more non-space/enter - // characters followed by a space/enter. - if (isSpaceOrEnter(code)) { - toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); - mPrevSymbolsKeyboardWasShifted = false; - } break; case SWITCH_STATE_SYMBOL: // Switch back to alpha keyboard mode if user types one or more non-space/enter // characters followed by a space/enter. - if (isSpaceOrEnter(code)) { + if (isSpaceOrEnter(code) && Settings.getInstance().getCurrent().mAlphaAfterSymbolAndSpace) { toggleAlphabetAndSymbols(autoCapsFlags, recapitalizeMode); mPrevSymbolsKeyboardWasShifted = false; } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt index a0b262f40..74a0137e1 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt @@ -149,6 +149,11 @@ class AdvancedSettingsFragment : SubScreenFragment() { customCurrencyDialog() true } + + findPreference("switch_after")?.setOnPreferenceClickListener { + switchToMainDialog() + true + } } override fun onStart() { @@ -485,6 +490,31 @@ class AdvancedSettingsFragment : SubScreenFragment() { d.show() } + private fun switchToMainDialog() { + val checked = booleanArrayOf( + sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, true), + sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_EMOJI, false), + sharedPreferences.getBoolean(Settings.PREF_ABC_AFTER_CLIP, false), + ) + val titles = arrayOf( + requireContext().getString(R.string.after_symbol_and_space), + requireContext().getString(R.string.after_emoji), + requireContext().getString(R.string.after_clip), + ) + AlertDialog.Builder(requireContext()) + .setTitle(R.string.switch_keyboard_after) + .setMultiChoiceItems(titles, checked) { _, i, b -> checked[i] = b } + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + sharedPreferences.edit { + putBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, checked[0]) + putBoolean(Settings.PREF_ABC_AFTER_EMOJI, checked[1]) + putBoolean(Settings.PREF_ABC_AFTER_CLIP, checked[2]) + } + } + .show() + } + private fun setupKeyLongpressTimeoutSettings() { val prefs = sharedPreferences findPreference(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy { diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 8059055c9..38a60abf0 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -156,6 +156,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_AUTO_SHOW_TOOLBAR = "auto_show_toolbar"; public static final String PREF_AUTO_HIDE_TOOLBAR = "auto_hide_toolbar"; public static final String PREF_CLIPBOARD_TOOLBAR_KEYS = "clipboard_toolbar_keys"; + public static final String PREF_ABC_AFTER_EMOJI = "abc_after_emoji"; + public static final String PREF_ABC_AFTER_CLIP = "abc_after_clip"; + public static final String PREF_ABC_AFTER_SYMBOL_SPACE = "abc_after_symbol_space"; // Emoji public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index cde3e2a9a..b3c6330c7 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -111,6 +111,9 @@ public class SettingsValues { public final float mBottomPaddingScale; public final boolean mAutoShowToolbar; public final boolean mAutoHideToolbar; + public final boolean mAlphaAfterEmojiInEmojiView; + public final boolean mAlphaAfterClipHistoryEntry; + public final boolean mAlphaAfterSymbolAndSpace; // From the input box @NonNull @@ -252,6 +255,9 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mAutoShowToolbar = prefs.getBoolean(Settings.PREF_AUTO_SHOW_TOOLBAR, false); mAutoHideToolbar = readSuggestionsEnabled(prefs) && prefs.getBoolean(Settings.PREF_AUTO_HIDE_TOOLBAR, false); mHasCustomFunctionalLayout = CustomLayoutUtilsKt.hasCustomFunctionalLayout(selectedSubtype, context); + mAlphaAfterEmojiInEmojiView = prefs.getBoolean(Settings.PREF_ABC_AFTER_EMOJI, false); + mAlphaAfterClipHistoryEntry = prefs.getBoolean(Settings.PREF_ABC_AFTER_CLIP, false); + mAlphaAfterSymbolAndSpace = prefs.getBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, true); } public boolean isApplicationSpecifiedCompletionsOn() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cff4bdf7..0f5360834 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -523,6 +523,14 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM Customize currencies Set main and up to 6 secondary currency symbols, separated with space + + Switch to main keyboard after… + + Selecting emoji in emoji view + + Selecting clipboard history entry + + Pressing enter or space after other keys in symbols view Set image for day or night mode? diff --git a/app/src/main/res/xml/prefs_screen_advanced.xml b/app/src/main/res/xml/prefs_screen_advanced.xml index d615c755c..d262c0c88 100644 --- a/app/src/main/res/xml/prefs_screen_advanced.xml +++ b/app/src/main/res/xml/prefs_screen_advanced.xml @@ -76,6 +76,11 @@ android:defaultValue="true" android:persistent="true" /> + + Date: Fri, 12 Jul 2024 11:51:42 -0600 Subject: [PATCH 87/92] Small changes to toolbar long-clicks (#959) * make "select all" key select word on long press * long-press 'copy' to cut text (instead of copy all) --- README.md | 2 +- .../main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt | 3 ++- app/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9b1eb4c0c..a0d4a0cc3 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Does not use internet permission, and thus is 100% offline. ## Hidden Functionality Features that may go unnoticed, and further potentially useful information -* Long-pressing toolbar keys results in additional functionality: clipboard -> paste, move left/right -> word left/right, move up/down -> page up/down, word left/right -> line start/end, page up/down -> page start/end, copy -> copy all, select word -> select all, undo <-> redo +* Long-pressing toolbar keys results in additional functionality: clipboard -> paste, move left/right -> word left/right, move up/down -> page up/down, word left/right -> line start/end, page up/down -> page start/end, copy -> cut, select word <-> select all, undo <-> redo * Long-press the Comma-key to access Clipboard View, Emoji View, One-handed Mode, Settings, or Switch Language: * Emoji View and Language Switch will disappear if you have the corresponding key enabled; * For some layouts it\'s not the Comma-key, but the key at the same position (e.g. it\'s `q` for Dvorak layout). diff --git a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt index 5b48b8135..6b02feac8 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt @@ -70,8 +70,9 @@ fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) { CLIPBOARD -> KeyCode.CLIPBOARD_PASTE UNDO -> KeyCode.REDO REDO -> KeyCode.UNDO + SELECT_ALL -> KeyCode.CLIPBOARD_SELECT_WORD SELECT_WORD -> KeyCode.CLIPBOARD_SELECT_ALL - COPY -> KeyCode.CLIPBOARD_COPY_ALL + COPY -> KeyCode.CLIPBOARD_CUT PASTE -> KeyCode.CLIPBOARD LEFT -> KeyCode.WORD_LEFT RIGHT -> KeyCode.WORD_RIGHT diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f5360834..4505f923e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -812,8 +812,8 @@ New dictionary: \n\t• move up/down &#65515; page up/down <br> \n\t• word left/right &#65515; line start/end <br> \n\t• page up/down &#65515; page start/end <br> -\n\t• copy &#65515; copy all <br> -\n\t• select word &#65515; select all <br> +\n\t• copy &#65515; cut <br> +\n\t• select word &#8596; select all <br> \n\t• undo &#8596; redo <br> <br> \n► Long-pressing keys in the suggestion strip toolbar pins them to the suggestion strip. <br> <br> \n► Long-press the Comma-key to access Clipboard View, Emoji View, One-handed Mode, Settings, or Switch Language: <br> From d7d47670a7aa229f29ba96d847aaa163e4e210e3 Mon Sep 17 00:00:00 2001 From: clmbmb <54553205+clmbmb@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:41:22 +0300 Subject: [PATCH 88/92] Remove language popup keys for other languages (#659) also removes potentially used keys for common foreign words, but this should be handled using the "more letters with diacritics" setting Co-authored-by: Helium314 --- .github/workflows/build-test-auto.yml | 2 +- app/src/main/assets/layouts/esperanto.txt | 10 +++---- app/src/main/assets/locale_key_texts/az.txt | 17 +++++------ app/src/main/assets/locale_key_texts/ca.txt | 14 ++++------ app/src/main/assets/locale_key_texts/cs.txt | 20 ++++++------- app/src/main/assets/locale_key_texts/da.txt | 13 ++------- .../main/assets/locale_key_texts/de-CH.txt | 9 ++---- .../main/assets/locale_key_texts/de-DE.txt | 10 +++---- app/src/main/assets/locale_key_texts/de.txt | 11 ++++---- app/src/main/assets/locale_key_texts/eo.txt | 26 ++++------------- app/src/main/assets/locale_key_texts/es.txt | 14 +++++----- app/src/main/assets/locale_key_texts/et.txt | 23 ++++----------- app/src/main/assets/locale_key_texts/eu.txt | 14 +++++----- app/src/main/assets/locale_key_texts/fi.txt | 9 +++--- app/src/main/assets/locale_key_texts/fr.txt | 14 +++++----- app/src/main/assets/locale_key_texts/gl.txt | 13 ++++----- app/src/main/assets/locale_key_texts/hr.txt | 7 ++--- app/src/main/assets/locale_key_texts/is.txt | 12 ++++---- app/src/main/assets/locale_key_texts/it.txt | 15 ++++------ app/src/main/assets/locale_key_texts/lt.txt | 23 +++++---------- app/src/main/assets/locale_key_texts/lv.txt | 26 ++++++++--------- app/src/main/assets/locale_key_texts/nb.txt | 7 ++--- app/src/main/assets/locale_key_texts/nl.txt | 13 ++++----- app/src/main/assets/locale_key_texts/pl.txt | 14 +++++----- app/src/main/assets/locale_key_texts/pms.txt | 15 ++++------ app/src/main/assets/locale_key_texts/pt.txt | 12 ++++---- app/src/main/assets/locale_key_texts/ro.txt | 6 ++-- app/src/main/assets/locale_key_texts/sk.txt | 28 +++++++++---------- .../main/assets/locale_key_texts/sr-Latn.txt | 10 +++---- app/src/main/assets/locale_key_texts/sv.txt | 20 +++---------- app/src/main/assets/locale_key_texts/sw.txt | 8 ------ app/src/main/assets/locale_key_texts/tl.txt | 8 +----- app/src/main/assets/locale_key_texts/tr.txt | 18 +++++------- app/src/main/assets/locale_key_texts/zu.txt | 9 +----- 34 files changed, 178 insertions(+), 292 deletions(-) diff --git a/.github/workflows/build-test-auto.yml b/.github/workflows/build-test-auto.yml index 19ddaee9b..a887aa5f8 100644 --- a/.github/workflows/build-test-auto.yml +++ b/.github/workflows/build-test-auto.yml @@ -10,7 +10,7 @@ on: # - 'app/**' pull_request: paths: - - 'app/**' + - 'app/src/main/java**' workflow_dispatch: jobs: diff --git a/app/src/main/assets/layouts/esperanto.txt b/app/src/main/assets/layouts/esperanto.txt index d000c2bf8..a725a22bb 100644 --- a/app/src/main/assets/layouts/esperanto.txt +++ b/app/src/main/assets/layouts/esperanto.txt @@ -1,9 +1,9 @@ -ŝ -ĝ +ŝ q +ĝ w e r t -ŭ +ŭ y u i o @@ -21,9 +21,9 @@ l ĵ z -ĉ +ĉ x c -v +v w b n m diff --git a/app/src/main/assets/locale_key_texts/az.txt b/app/src/main/assets/locale_key_texts/az.txt index 6a3e684dd..d5ca568a6 100644 --- a/app/src/main/assets/locale_key_texts/az.txt +++ b/app/src/main/assets/locale_key_texts/az.txt @@ -1,12 +1,9 @@ [popup_keys] -a â ä á -e ə é -i ı î ï ì í į ī -o ö ô œ ò ó õ ø ō -u ü û ù ú ū -s ş ß ś š +e ə +i ı +ı i +o ö +u ü +s ş g ğ -n ň ñ -c ç ć č -y ý -z ž +c ç diff --git a/app/src/main/assets/locale_key_texts/ca.txt b/app/src/main/assets/locale_key_texts/ca.txt index ded408ce1..9eccfc28d 100644 --- a/app/src/main/assets/locale_key_texts/ca.txt +++ b/app/src/main/assets/locale_key_texts/ca.txt @@ -1,12 +1,10 @@ [popup_keys] -a à á ä â ã å ą æ ā ª -e è é ë ê ę ė ē -i í ï ì î į ī -o ò ó ö ô õ ø œ ō º -u ú ü ù û ū -n ñ ń -c ç ć č -l l·l ł +a à +e è é +i í ï +o ò ó +u ú ü +c ç punctuation !autoColumnOrder!9 \, ? ! · # ) ( / ; ' @ : - " + \% & [extra_keys] diff --git a/app/src/main/assets/locale_key_texts/cs.txt b/app/src/main/assets/locale_key_texts/cs.txt index d0198e28e..83e844e6f 100644 --- a/app/src/main/assets/locale_key_texts/cs.txt +++ b/app/src/main/assets/locale_key_texts/cs.txt @@ -1,16 +1,16 @@ [popup_keys] -a á à â ä æ ã å ā -e é ě è ê ë ę ė ē -i í î ï ì į ī -o ó ö ô ò õ œ ø ō -u ú ů û ü ù ū -s š ß ś -n ň ñ ń -c č ç ć -y ý ÿ +a á +c č d ď +e é ě +i í +n ň +o ó r ř +s š t ť -z ž ź ż +u ú ů +y ý +z ž ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/da.txt b/app/src/main/assets/locale_key_texts/da.txt index 7ddca44d4..b12cd4160 100644 --- a/app/src/main/assets/locale_key_texts/da.txt +++ b/app/src/main/assets/locale_key_texts/da.txt @@ -1,14 +1,7 @@ [popup_keys] -a å æ á ä à â ã ā -e é ë -i í ï -o ø ö ó ô ò õ œ ō -u ú ü û ù ū -s ß ś š -n ñ ń -y ý ÿ -d ð -l ł +a å æ +e é +o ø ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/de-CH.txt b/app/src/main/assets/locale_key_texts/de-CH.txt index cc8bd0df0..6e452594d 100644 --- a/app/src/main/assets/locale_key_texts/de-CH.txt +++ b/app/src/main/assets/locale_key_texts/de-CH.txt @@ -1,10 +1,7 @@ [popup_keys] -a ä â à á æ ã å ā -e é è ê ë ė -o ö ô ò ó õ œ ø ō -u ü û ù ú ū -s ß % ś š -n ñ ń +a ä +o ö +u ü ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/de-DE.txt b/app/src/main/assets/locale_key_texts/de-DE.txt index a3465bb1a..c81f96333 100644 --- a/app/src/main/assets/locale_key_texts/de-DE.txt +++ b/app/src/main/assets/locale_key_texts/de-DE.txt @@ -1,10 +1,8 @@ [popup_keys] -a ä â à á æ ã å ā -e é è ê ë ė -o ö ô ò ó õ œ ø ō -u ü û ù ú ū -s ß % ś š -n ñ ń +a ä +o ö +u ü +s ß ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/de.txt b/app/src/main/assets/locale_key_texts/de.txt index 144743d16..965b69389 100644 --- a/app/src/main/assets/locale_key_texts/de.txt +++ b/app/src/main/assets/locale_key_texts/de.txt @@ -1,9 +1,8 @@ [popup_keys] -a ä % â à á æ ã å ā -e é è ê ë ė -o ö % ô ò ó õ œ ø ō -u ü % û ù ú ū -s ß % ś š -n ñ ń +a ä +e +o ö +u ü +s ß ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/eo.txt b/app/src/main/assets/locale_key_texts/eo.txt index f8ca3f4b4..f2fda7e40 100644 --- a/app/src/main/assets/locale_key_texts/eo.txt +++ b/app/src/main/assets/locale_key_texts/eo.txt @@ -1,23 +1,7 @@ [popup_keys] -a á à â ä æ ã å ā ă ą ª -e é ě è ê ë ę ė ē -i í î ï ĩ ì į ī ı ij j ĵ -o ó ö ô ò õ œ ø ō ő º -u ŭ ú ů û ü ù ū ũ ű ų µ -s ŝ ß š ś ș ş -n ñ ń ņ ň ʼn ŋ -c ĉ ć č ç ċ -ŭ y ý ŷ ÿ þ -d ð ď đ -r ř ŕ ŗ -t ť ț ţ ŧ -z ź ż ž -k ķ ĸ -l ĺ ļ ľ ŀ ł -g ĝ ğ ġ ģ -v w ŵ -h ĥ ħ -ĝ w ŵ -ŝ q -ĉ x +u ŭ +s ŝ +c ĉ +g ĝ +h ĥ diff --git a/app/src/main/assets/locale_key_texts/es.txt b/app/src/main/assets/locale_key_texts/es.txt index 22682a493..5e66d9810 100644 --- a/app/src/main/assets/locale_key_texts/es.txt +++ b/app/src/main/assets/locale_key_texts/es.txt @@ -1,11 +1,11 @@ [popup_keys] -a á à ä â ã å ą æ ā ª -e é è ë ê ę ė ē -i í ï ì î į ī -o ó ò ö ô õ ø œ ō º -u ú ü ù û ū -n ñ ń -c ç ć č +a á ª +e é +i í +o ó º +u ú ü +n ñ +y ý punctuation !autoColumnOrder!9 \, ? ! # ) ( / ; ¡ ' @ : - " + \% & ¿ [extra_keys] diff --git a/app/src/main/assets/locale_key_texts/et.txt b/app/src/main/assets/locale_key_texts/et.txt index 598595f28..0ad69c235 100644 --- a/app/src/main/assets/locale_key_texts/et.txt +++ b/app/src/main/assets/locale_key_texts/et.txt @@ -1,20 +1,9 @@ [popup_keys] -a ä ā à á â ã å æ ą -e ē è ė é ê ë ę ě -i ī ì į í î ï ı -o ö õ ò ó ô œ ő ø -u ü ū ų ù ú û ů ű -s š ß ś ş -n ņ ñ ń -c č ç ć -y ý ÿ -d ď -r ŗ ř ŕ -t ţ ť -z ž ż ź -k ķ -l ļ ł ĺ ľ -g ģ ğ +a ä +o ö õ +u ü +s š +z ž ' ’ ‚ ‘ " ” „ “ @@ -22,4 +11,4 @@ g ģ ğ 1: ü 2: ö õ 2: ä -3: õ \ No newline at end of file +3: õ diff --git a/app/src/main/assets/locale_key_texts/eu.txt b/app/src/main/assets/locale_key_texts/eu.txt index 5cd8baba5..4cf01613c 100644 --- a/app/src/main/assets/locale_key_texts/eu.txt +++ b/app/src/main/assets/locale_key_texts/eu.txt @@ -1,11 +1,11 @@ [popup_keys] -a á à ä â ã å ą æ ā ª -e é è ë ê ę ė ē -i í ï ì î į ī -o ó ò ö ô õ ø œ ō º -u ú ü ù û ū -n ñ ń -c ç ć č +a á ª +e é +i í +o ó º +u ú ü û +n ñ +c ç [extra_keys] 2: ñ diff --git a/app/src/main/assets/locale_key_texts/fi.txt b/app/src/main/assets/locale_key_texts/fi.txt index 0049508b9..bfb587327 100644 --- a/app/src/main/assets/locale_key_texts/fi.txt +++ b/app/src/main/assets/locale_key_texts/fi.txt @@ -1,9 +1,8 @@ [popup_keys] -a ä å æ à á â ã ā -o ö ø ô ò ó õ œ ō -u ü -s š ß ś -z ž ź ż +a ä å +o ö +s š +z ž [extra_keys] 1: å diff --git a/app/src/main/assets/locale_key_texts/fr.txt b/app/src/main/assets/locale_key_texts/fr.txt index 1bc0a4b75..4311b0918 100644 --- a/app/src/main/assets/locale_key_texts/fr.txt +++ b/app/src/main/assets/locale_key_texts/fr.txt @@ -1,11 +1,11 @@ [popup_keys] -a à â % æ á ä ã å ā ª -e é è ê ë % ę ė ē -i î % ï ì í į ī -o ô œ % ö ò ó õ ø ō º -u ù û % ü ú ū -c ç % ć č -y % ÿ +a à â æ +e é è ê ë +i î ï +o ô œ +u ù û ü +c ç +y ÿ [extra_keys] 1: è ü diff --git a/app/src/main/assets/locale_key_texts/gl.txt b/app/src/main/assets/locale_key_texts/gl.txt index 5cd8baba5..421b3f706 100644 --- a/app/src/main/assets/locale_key_texts/gl.txt +++ b/app/src/main/assets/locale_key_texts/gl.txt @@ -1,11 +1,10 @@ [popup_keys] -a á à ä â ã å ą æ ā ª -e é è ë ê ę ė ē -i í ï ì î į ī -o ó ò ö ô õ ø œ ō º -u ú ü ù û ū -n ñ ń -c ç ć č +a á ª +e é +i í +o ó º +u ú ü +n ñ [extra_keys] 2: ñ diff --git a/app/src/main/assets/locale_key_texts/hr.txt b/app/src/main/assets/locale_key_texts/hr.txt index 641abe7e9..2f20257ae 100644 --- a/app/src/main/assets/locale_key_texts/hr.txt +++ b/app/src/main/assets/locale_key_texts/hr.txt @@ -1,8 +1,7 @@ [popup_keys] -s š ś ß -n ñ ń -z ž ź ż -c č ć ç +s š +z ž +c č ć d đ ' ‘ ‚ ’ › ‹ " “ „ ” » « diff --git a/app/src/main/assets/locale_key_texts/is.txt b/app/src/main/assets/locale_key_texts/is.txt index c54669d7d..b8380b2e1 100644 --- a/app/src/main/assets/locale_key_texts/is.txt +++ b/app/src/main/assets/locale_key_texts/is.txt @@ -1,11 +1,11 @@ [popup_keys] -a á ä æ å à â ã ā -e é ë è ê ę ė ē -i í ï î ì į ī -o ó ö ô ò õ œ ø ō -u ú ü û ù ū -y ý ÿ +a á æ d ð +e é +i í +o ó ö +u ú +y ý t þ ' ’ ‚ ‘ " ” „ “ diff --git a/app/src/main/assets/locale_key_texts/it.txt b/app/src/main/assets/locale_key_texts/it.txt index e6773c542..72973870b 100644 --- a/app/src/main/assets/locale_key_texts/it.txt +++ b/app/src/main/assets/locale_key_texts/it.txt @@ -1,11 +1,6 @@ [popup_keys] -a à á â ä æ ã å ā ª -e è é ê ë ę ė ē ə -i ì í î ï į ī -o ò ó ô ö õ œ ø ō º -u ù ú û ü ū - -[extra_keys] -1: ü è -2: ö é -2: ä à +a à ª +e è é +i ì +o ò ó º +u ù diff --git a/app/src/main/assets/locale_key_texts/lt.txt b/app/src/main/assets/locale_key_texts/lt.txt index 22499999f..a55b16039 100644 --- a/app/src/main/assets/locale_key_texts/lt.txt +++ b/app/src/main/assets/locale_key_texts/lt.txt @@ -1,19 +1,10 @@ [popup_keys] -a ą ä ā à á â ã å æ -e ė ę ē è é ê ë ě -i į ī ì í î ï ı -o ö õ ò ó ô œ ő ø -u ū ų ü ū ù ú û ů ű -s š ß ś ş -n ņ ñ ń -c č ç ć -y ý ÿ -d ď -r ŗ ř ŕ -t ţ ť -z ž ż ź -k ķ -l ļ ł ĺ ľ -g ģ ğ +a ą +c č +e ė ę +i į +s š +u ū ų +z ž ' ’ ‚ ‘ " ” „ “ diff --git a/app/src/main/assets/locale_key_texts/lv.txt b/app/src/main/assets/locale_key_texts/lv.txt index fe390ce0b..cf6f53653 100644 --- a/app/src/main/assets/locale_key_texts/lv.txt +++ b/app/src/main/assets/locale_key_texts/lv.txt @@ -1,19 +1,15 @@ [popup_keys] -a ā à á â ã ä å æ ą -e ē ė è é ê ë ę ě -i ī į ì í î ï ı -o ò ó ô õ ö œ ő ø -u ū ų ù ú û ü ů ű -s š ß ś ş -n ņ ñ ń -c č ç ć -y ý ÿ -d ď -r ŗ ř ŕ -t ţ ť -z ž ż ź +a ā +c č +e ē +g ģ +i ī k ķ -l ļ ł ĺ ľ -g ģ ğ +l ļ +n ņ +o ō +s š +u ū +z ž ' ’ ‚ ‘ " ” „ “ diff --git a/app/src/main/assets/locale_key_texts/nb.txt b/app/src/main/assets/locale_key_texts/nb.txt index 55f7af945..05c9c8773 100644 --- a/app/src/main/assets/locale_key_texts/nb.txt +++ b/app/src/main/assets/locale_key_texts/nb.txt @@ -1,8 +1,7 @@ [popup_keys] -a å æ ä à á â ã ā -e é è ê ë ę ė ē -o ø ö ô ò ó õ œ ō -u ü û ù ú ū +a å æ +e é +o ø ' ‘ ‚ ’ " ” „ “ « » diff --git a/app/src/main/assets/locale_key_texts/nl.txt b/app/src/main/assets/locale_key_texts/nl.txt index ccaa502e3..aa4f1a968 100644 --- a/app/src/main/assets/locale_key_texts/nl.txt +++ b/app/src/main/assets/locale_key_texts/nl.txt @@ -1,10 +1,9 @@ [popup_keys] -a á ä â à æ ã å ā -e é ë ê è ę ė ē -i í ï ì î į ī ij -o ó ö ô ò õ œ ø ō -u ú ü û ù ū -n ñ ń -y ij +a á ä â à +e é ë ê è +i í ï ì î ij +o ó ö ô ò +u ú ü û ù +y ý ÿ ' ‘ ‚ ’ " “ „ ” diff --git a/app/src/main/assets/locale_key_texts/pl.txt b/app/src/main/assets/locale_key_texts/pl.txt index e96d1acdb..a92ce9c4f 100644 --- a/app/src/main/assets/locale_key_texts/pl.txt +++ b/app/src/main/assets/locale_key_texts/pl.txt @@ -1,11 +1,11 @@ [popup_keys] -a ą á à â ä æ ã å ā -e ę è é ê ë ė ē -o ó ö ô ò õ œ ø ō -s ś ß š -n ń ñ -c ć ç č -z ż ź ž +a ą +e ę +o ó +s ś +n ń +c ć +z ż ź l ł ' ‘ ‚ ’ " “ „ ” diff --git a/app/src/main/assets/locale_key_texts/pms.txt b/app/src/main/assets/locale_key_texts/pms.txt index 6612e0e1b..2fa1e4a7a 100644 --- a/app/src/main/assets/locale_key_texts/pms.txt +++ b/app/src/main/assets/locale_key_texts/pms.txt @@ -1,11 +1,6 @@ [popup_keys] -a à á â ä æ ã å ā ª -e è é ê ë ę ė ē ə -i ì í î ï į ī -o ò ó ô ö õ œ ø ō º -u ù ú û ü ū - -[extra_keys] -1: ü è -2: ö é -2: ë à +a à +e é ë è +i ì +o ö ò +u ü ù diff --git a/app/src/main/assets/locale_key_texts/pt.txt b/app/src/main/assets/locale_key_texts/pt.txt index 7d0247ed6..ff73ef434 100644 --- a/app/src/main/assets/locale_key_texts/pt.txt +++ b/app/src/main/assets/locale_key_texts/pt.txt @@ -1,7 +1,7 @@ [popup_keys] -a á ã à â ä å æ ª -e é ê è ę ė ē ë -i í î ì ï į ī -o ó õ ô ò ö œ ø ō º -u ú ü ù û ū -c ç č ć +a á ã à â ª +e é ê +i í +o ó õ ô º +u ú ü +c ç diff --git a/app/src/main/assets/locale_key_texts/ro.txt b/app/src/main/assets/locale_key_texts/ro.txt index 3d0ed9711..49b4c2efa 100644 --- a/app/src/main/assets/locale_key_texts/ro.txt +++ b/app/src/main/assets/locale_key_texts/ro.txt @@ -1,7 +1,7 @@ [popup_keys] -a ă â ã à á ä æ å ā -i î ï ì í į ī -s ș ß ś š +a ă â +i î +s ș t ț ' ‘ ‚ ’ " “ „ ” diff --git a/app/src/main/assets/locale_key_texts/sk.txt b/app/src/main/assets/locale_key_texts/sk.txt index 48518a179..36ec252bb 100644 --- a/app/src/main/assets/locale_key_texts/sk.txt +++ b/app/src/main/assets/locale_key_texts/sk.txt @@ -1,19 +1,17 @@ [popup_keys] -a á ä ā à â ã å æ ą -e é ě ē ė è ê ë ę -i í ī į ì î ï ı -o ô ó ö ò õ œ ő ø -u ú ů ü ū ų ù û ű -s š ß ś ş -n ň ņ ñ ń -c č ç ć -y ý ÿ +a á ä +e é +i í +o ô ó +u ú +s š +n ň +c č +y ý d ď -r ŕ ř ŗ -t ť ţ -z ž ż ź -k ķ -l ľ ĺ ļ ł -g ģ ğ +r ŕ +t ť +z ž +l ľ ĺ ' ’ ‚ ‘ › ‹ " ” „ “ » « diff --git a/app/src/main/assets/locale_key_texts/sr-Latn.txt b/app/src/main/assets/locale_key_texts/sr-Latn.txt index dfbcac470..ecce81e47 100644 --- a/app/src/main/assets/locale_key_texts/sr-Latn.txt +++ b/app/src/main/assets/locale_key_texts/sr-Latn.txt @@ -1,10 +1,8 @@ [popup_keys] -e è -i ì -s š % -c č ć % -d đ % -z ž % +s š +z ž +c č ć +d đ [extra_keys] 1: š diff --git a/app/src/main/assets/locale_key_texts/sv.txt b/app/src/main/assets/locale_key_texts/sv.txt index a1438e1f0..0425538d2 100644 --- a/app/src/main/assets/locale_key_texts/sv.txt +++ b/app/src/main/assets/locale_key_texts/sv.txt @@ -1,22 +1,10 @@ [popup_keys] -a ä å æ á à â ą ã -c ç ć č -d ð ď -e é è ê ë ę -i í ì î ï -l ł -n ń ñ ň -o ö ø œ ó ò ô õ ō -r ř -s ś š ş ß -t ť þ -u ü ú ù û ū -y ý ÿ -z ź ž ż +a ä å +o ö ' › ‹ " » « [extra_keys] 1: å -2: ö ø œ -2: ä æ +2: ö +2: ä diff --git a/app/src/main/assets/locale_key_texts/sw.txt b/app/src/main/assets/locale_key_texts/sw.txt index bc7ccd77b..d36014bf7 100644 --- a/app/src/main/assets/locale_key_texts/sw.txt +++ b/app/src/main/assets/locale_key_texts/sw.txt @@ -1,10 +1,2 @@ [popup_keys] -a à á â ä æ ã å ā -e è é ê ë ē -i î ï í ī ì -o ô ö ò ó œ ø ō õ -u û ü ù ú ū -s ß -n ñ -c ç g g\' diff --git a/app/src/main/assets/locale_key_texts/tl.txt b/app/src/main/assets/locale_key_texts/tl.txt index 5cd8baba5..f111e2622 100644 --- a/app/src/main/assets/locale_key_texts/tl.txt +++ b/app/src/main/assets/locale_key_texts/tl.txt @@ -1,11 +1,5 @@ [popup_keys] -a á à ä â ã å ą æ ā ª -e é è ë ê ę ė ē -i í ï ì î į ī -o ó ò ö ô õ ø œ ō º -u ú ü ù û ū -n ñ ń -c ç ć č +n ñ [extra_keys] 2: ñ diff --git a/app/src/main/assets/locale_key_texts/tr.txt b/app/src/main/assets/locale_key_texts/tr.txt index f9c797a25..a729bd4d1 100644 --- a/app/src/main/assets/locale_key_texts/tr.txt +++ b/app/src/main/assets/locale_key_texts/tr.txt @@ -1,13 +1,9 @@ [popup_keys] -a â ä á -e ə é -ı i î ï ì í į ī -i ı î ï ì í į ī -o ö ô œ ò ó õ ø ō -u ü û ù ú ū -s ş ß ś š +a â +ı i î +i ı î +o ö +u ü û +s ş g ğ -n ň ñ -c ç ć č -y ý -z ž +c ç diff --git a/app/src/main/assets/locale_key_texts/zu.txt b/app/src/main/assets/locale_key_texts/zu.txt index a091434a5..ccc06327d 100644 --- a/app/src/main/assets/locale_key_texts/zu.txt +++ b/app/src/main/assets/locale_key_texts/zu.txt @@ -1,9 +1,2 @@ [popup_keys] -a à á â ä æ ã å ā -e é è ê ë ē -i í î ï ī ì -o ó ô ö ò œ ø ō õ -u ú û ü ù ū -s ß -n ñ -c ç +b ɓ From 5378d8d0a3cb339fd873a91b3e98d3c29e65ed4b Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 12 Jul 2024 20:55:38 +0200 Subject: [PATCH 89/92] add another option to the "more letters with diacritics" settings with letters that used to be default in many languages now this setting is default --- app/src/main/assets/locale_key_texts/hi-Latn.txt | 9 --------- .../{all_popup_keys.txt => more_popups_all.txt} | 0 .../locale_key_texts/{en.txt => more_popups_main.txt} | 0 .../{more_popup_keys.txt => more_popups_more.txt} | 0 .../internal/keyboard_parser/LocaleKeyboardInfos.kt | 7 +++++-- .../java/helium314/keyboard/latin/settings/Settings.java | 5 +++-- app/src/main/res/values/donottranslate.xml | 2 ++ app/src/main/res/values/strings.xml | 4 +++- app/src/main/res/xml/prefs_screen_advanced.xml | 2 +- 9 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 app/src/main/assets/locale_key_texts/hi-Latn.txt rename app/src/main/assets/locale_key_texts/{all_popup_keys.txt => more_popups_all.txt} (100%) rename app/src/main/assets/locale_key_texts/{en.txt => more_popups_main.txt} (100%) rename app/src/main/assets/locale_key_texts/{more_popup_keys.txt => more_popups_more.txt} (100%) diff --git a/app/src/main/assets/locale_key_texts/hi-Latn.txt b/app/src/main/assets/locale_key_texts/hi-Latn.txt deleted file mode 100644 index a091434a5..000000000 --- a/app/src/main/assets/locale_key_texts/hi-Latn.txt +++ /dev/null @@ -1,9 +0,0 @@ -[popup_keys] -a à á â ä æ ã å ā -e é è ê ë ē -i í î ï ī ì -o ó ô ö ò œ ø ō õ -u ú û ü ù ū -s ß -n ñ -c ç diff --git a/app/src/main/assets/locale_key_texts/all_popup_keys.txt b/app/src/main/assets/locale_key_texts/more_popups_all.txt similarity index 100% rename from app/src/main/assets/locale_key_texts/all_popup_keys.txt rename to app/src/main/assets/locale_key_texts/more_popups_all.txt diff --git a/app/src/main/assets/locale_key_texts/en.txt b/app/src/main/assets/locale_key_texts/more_popups_main.txt similarity index 100% rename from app/src/main/assets/locale_key_texts/en.txt rename to app/src/main/assets/locale_key_texts/more_popups_main.txt diff --git a/app/src/main/assets/locale_key_texts/more_popup_keys.txt b/app/src/main/assets/locale_key_texts/more_popups_more.txt similarity index 100% rename from app/src/main/assets/locale_key_texts/more_popup_keys.txt rename to app/src/main/assets/locale_key_texts/more_popups_more.txt diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index 766aeab60..ebf5ac382 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -239,9 +239,11 @@ fun addLocaleKeyTextsToParams(context: Context, params: KeyboardParams, popupKey private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popupKeysSetting: Int): LocaleKeyboardInfos { val lkt = LocaleKeyboardInfos(getStreamForLocale(params.mId.locale, context), params.mId.locale) if (popupKeysSetting == POPUP_KEYS_MORE) - lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/all_popup_keys.txt")) + lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt")) else if (popupKeysSetting == POPUP_KEYS_ALL) - lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popup_keys.txt")) + lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt")) + else if (popupKeysSetting == POPUP_KEYS_MAIN) + lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_main.txt")) params.mSecondaryLocales.forEach { locale -> if (locale == params.mId.locale) return@forEach lkt.addFile(getStreamForLocale(locale, context)) @@ -339,6 +341,7 @@ private val euroLocales = "bg|ca|cs|da|de|el|en|es|et|eu|fi|fr|ga|gl|hr|hu|it|lb const val POPUP_KEYS_ALL = 2 const val POPUP_KEYS_MORE = 1 +const val POPUP_KEYS_MAIN = 3 const val POPUP_KEYS_NORMAL = 0 private const val LOCALE_TEXTS_FOLDER = "locale_key_texts" diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 38a60abf0..440e7cb3f 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -507,10 +507,11 @@ public static void writePinnedClipString(final Context context, final String cli } public static int readMorePopupKeysPref(final SharedPreferences prefs) { - return switch (prefs.getString(Settings.PREF_MORE_POPUP_KEYS, "normal")) { + return switch (prefs.getString(Settings.PREF_MORE_POPUP_KEYS, "main")) { case "all" -> LocaleKeyboardInfosKt.POPUP_KEYS_ALL; case "more" -> LocaleKeyboardInfosKt.POPUP_KEYS_MORE; - default -> LocaleKeyboardInfosKt.POPUP_KEYS_NORMAL; + case "normal" -> LocaleKeyboardInfosKt.POPUP_KEYS_NORMAL; + default -> LocaleKeyboardInfosKt.POPUP_KEYS_MAIN; }; } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index a5df734f3..aad83ab52 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -99,11 +99,13 @@
    normal + main more all @string/show_popup_keys_normal + @string/show_popup_keys_main @string/show_popup_keys_more @string/show_popup_keys_all diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4505f923e..e785dfe42 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -200,7 +200,9 @@ Show more letters with diacritics in popup - Show variants defined in keyboard languages (default) + Show variants defined in keyboard languages + + Add very common variants (default) Add common variants diff --git a/app/src/main/res/xml/prefs_screen_advanced.xml b/app/src/main/res/xml/prefs_screen_advanced.xml index d262c0c88..62e89b1a2 100644 --- a/app/src/main/res/xml/prefs_screen_advanced.xml +++ b/app/src/main/res/xml/prefs_screen_advanced.xml @@ -91,7 +91,7 @@ android:title="@string/show_popup_keys_title" android:entries="@array/show_popup_keys_entries" android:entryValues="@array/show_popup_keys_values" - android:defaultValue="normal" + android:defaultValue="main" android:summary="%s" android:persistent="true" latin:singleLineTitle="false" /> From b9e116ed13f101500ca5221601e7d23f670ea23e Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 12 Jul 2024 22:25:46 +0200 Subject: [PATCH 90/92] adjust how priority language popups work (follow-up to #659) --- .../main/assets/locale_key_texts/bn-BD.txt | 20 ++-- .../main/assets/locale_key_texts/bn-IN.txt | 8 +- app/src/main/assets/locale_key_texts/el.txt | 8 +- app/src/main/assets/locale_key_texts/hi.txt | 34 +++--- app/src/main/assets/locale_key_texts/kn.txt | 20 ++-- app/src/main/assets/locale_key_texts/ml.txt | 20 ++-- app/src/main/assets/locale_key_texts/mr.txt | 18 ++-- app/src/main/assets/locale_key_texts/ta.txt | 10 +- app/src/main/assets/locale_key_texts/te.txt | 20 ++-- .../keyboard_parser/LocaleKeyboardInfos.kt | 100 +++++++++--------- 10 files changed, 131 insertions(+), 127 deletions(-) diff --git a/app/src/main/assets/locale_key_texts/bn-BD.txt b/app/src/main/assets/locale_key_texts/bn-BD.txt index 99a6e968d..6f602cf66 100644 --- a/app/src/main/assets/locale_key_texts/bn-BD.txt +++ b/app/src/main/assets/locale_key_texts/bn-BD.txt @@ -1,14 +1,14 @@ [popup_keys] -ঙ ং % -য য় % -ড ঢ % -প ফ % -ট ঠ % -চ ছ % -জ ঝ % -হ ঞ % -গ ঘ % -ড় ঢ় % +ঙ ং +য য় +ড ঢ +প ফ +ট ঠ +চ ছ +জ ঝ +হ ঞ +গ ঘ +ড় ঢ় ৃ ঋ ু উ ূ ঊ diff --git a/app/src/main/assets/locale_key_texts/bn-IN.txt b/app/src/main/assets/locale_key_texts/bn-IN.txt index 77fdac212..36387a9e7 100644 --- a/app/src/main/assets/locale_key_texts/bn-IN.txt +++ b/app/src/main/assets/locale_key_texts/bn-IN.txt @@ -1,8 +1,8 @@ [popup_keys] -ব ভ % -গ ঘ % -দ ধ % -জ ঝ জ্ঞ % +ব ভ +গ ঘ +দ ধ +জ ঝ জ্ঞ ড ড় ও ো এ ে diff --git a/app/src/main/assets/locale_key_texts/el.txt b/app/src/main/assets/locale_key_texts/el.txt index 4db3f77dd..6372cd786 100644 --- a/app/src/main/assets/locale_key_texts/el.txt +++ b/app/src/main/assets/locale_key_texts/el.txt @@ -1,8 +1,8 @@ [popup_keys] -ε έ % -υ ύ % ϋ ΰ -ι ί % ϊ ΐ -ο ό % +ε έ +υ ύ ϋ ΰ +ι ί ϊ ΐ +ο ό α ά η ή ω ώ diff --git a/app/src/main/assets/locale_key_texts/hi.txt b/app/src/main/assets/locale_key_texts/hi.txt index 4514c00a5..900946954 100644 --- a/app/src/main/assets/locale_key_texts/hi.txt +++ b/app/src/main/assets/locale_key_texts/hi.txt @@ -5,14 +5,14 @@ ई ईं ऊ ऊं ऊँ ध क्ष श्र -ौ ौं % -ै ैं % -ा ां ाँ % -ी ीं % -ू ूं ूँ % -ब ब॒ % -ग ज्ञ ग़ ग॒ % -ज ज॒ ज्ञ ज़ % +ौ ौं +ै ैं +ा ां ाँ +ी ीं +ू ूं ूँ +ब ब॒ +ग ज्ञ ग़ ग॒ +ज ज॒ ज्ञ ज़ ड ड॒ ड़ ओ ओं ऑ ऒ ए एं एँ ऍ ऎ @@ -38,15 +38,15 @@ य य़ ़ ॽ ॰ ऽ punctuation !autoColumnOrder!9 \, . ? ! # ) ( / ; ' @ : - " + \% & -औ ौ % -ऐ ै % -आ ा % -ई ी % -ऊ ू % -ब भ % -ग घ % -द ध % -ज झ ज्ञ % +औ ौ +ऐ ै +आ ा +ई ी +ऊ ू +ब भ +ग घ +द ध +ज झ ज्ञ ड ढ ओ ो ए े diff --git a/app/src/main/assets/locale_key_texts/kn.txt b/app/src/main/assets/locale_key_texts/kn.txt index f899054ac..237a2c4ad 100644 --- a/app/src/main/assets/locale_key_texts/kn.txt +++ b/app/src/main/assets/locale_key_texts/kn.txt @@ -1,14 +1,14 @@ [popup_keys] -ಅ % -ಆ ಾ % -ಇ ಿ % -ಈ ೀ % -ಉ ು % -ಊ ೂ % -ಋ ೄ ೃ ೠ % -ಎ ೆ % -ಏ ೇ % -ಐ ೖ ೈ % +ಅ +ಆ ಾ +ಇ ಿ +ಈ ೀ +ಉ ು +ಊ ೂ +ಋ ೄ ೃ ೠ +ಎ ೆ +ಏ ೇ +ಐ ೖ ೈ ಒ ೊ ಓ ೋ ಔ ೌ diff --git a/app/src/main/assets/locale_key_texts/ml.txt b/app/src/main/assets/locale_key_texts/ml.txt index 7782adc75..a227c63a4 100644 --- a/app/src/main/assets/locale_key_texts/ml.txt +++ b/app/src/main/assets/locale_key_texts/ml.txt @@ -1,14 +1,14 @@ [popup_keys] -് അ % -ാ ആ % -ി ഇ % -ീ ഈ % -ു ഉ % -ൂ ഊ % -ൃ ഋ % -െ എ ഐ ൈ % -േ ഏ % -ൊ ഒ % +് അ +ാ ആ +ി ഇ +ീ ഈ +ു ഉ +ൂ ഊ +ൃ ഋ +െ എ ഐ ൈ +േ ഏ +ൊ ഒ ോ ഓ ഔ ൗ ക ഖ ഗ ഘ diff --git a/app/src/main/assets/locale_key_texts/mr.txt b/app/src/main/assets/locale_key_texts/mr.txt index b8455c566..db6c8b72a 100644 --- a/app/src/main/assets/locale_key_texts/mr.txt +++ b/app/src/main/assets/locale_key_texts/mr.txt @@ -1,13 +1,13 @@ [popup_keys] -ौ औ % -ै ऐ % -ा आ % -ी ई % -ू ऊ % -ब भ % -ग घ % -द ध % -ज झ ज्ञ % +ौ औ +ै ऐ +ा आ +ी ई +ू ऊ +ब भ +ग घ +द ध +ज झ ज्ञ ड ढ ो ओ े ए diff --git a/app/src/main/assets/locale_key_texts/ta.txt b/app/src/main/assets/locale_key_texts/ta.txt index 4d692380a..5c2d9ecb2 100644 --- a/app/src/main/assets/locale_key_texts/ta.txt +++ b/app/src/main/assets/locale_key_texts/ta.txt @@ -1,9 +1,9 @@ [popup_keys] -ஔ ௌ % -ஐ ை % -ஆ ா % -ஈ ீ % -ஊ ூ % +ஔ ௌ +ஐ ை +ஆ ா +ஈ ீ +ஊ ூ ஓ ோ ௐ ஏ ே அ ஃ diff --git a/app/src/main/assets/locale_key_texts/te.txt b/app/src/main/assets/locale_key_texts/te.txt index c86b937c7..baf480a39 100644 --- a/app/src/main/assets/locale_key_texts/te.txt +++ b/app/src/main/assets/locale_key_texts/te.txt @@ -1,14 +1,14 @@ [popup_keys] -ౌ ఔ % -ై ఐ % -ా ఆ % -ీ ఈ % -ూ ఊ % -బ భ % -హ ః % -గ ఘ % -ద ధ % -జ ఝ % +ౌ ఔ +ై ఐ +ా ఆ +ీ ఈ +ూ ఊ +బ భ +హ ః +గ ఘ +ద ధ +జ ఝ డ ఢ ో ఓ ే ఏ diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index ebf5ac382..722038061 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -16,8 +16,8 @@ import java.util.Locale import kotlin.math.round class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { - private val popupKeys = hashMapOf>() - private val priorityPopupKeys = hashMapOf>() + private val popupKeys = hashMapOf>() + private val priorityPopupKeys = hashMapOf>() private val extraKeys = Array?>(5) { null } var labelSymbol = "\\?123" private set @@ -57,22 +57,23 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { } init { - readStream(dataStream, false) + readStream(dataStream, false, true) // set default quote popupKeys if necessary // should this also be done with punctuation popupKeys? + // todo: those defaults should not be in here if ("\'" !in popupKeys) - popupKeys["\'"] = listOf("!fixedColumnOrder!5", "‚", "‘", "’", "‹", "›") + popupKeys["\'"] = mutableListOf("!fixedColumnOrder!5", "‚", "‘", "’", "‹", "›") if ("\"" !in popupKeys) - popupKeys["\""] = listOf("!fixedColumnOrder!5", "„", "“", "”", "«", "»") + popupKeys["\""] = mutableListOf("!fixedColumnOrder!5", "„", "“", "”", "«", "»") if ("!" !in popupKeys) - popupKeys["!"] = listOf("¡") + popupKeys["!"] = mutableListOf("¡") if (labelQuestion !in popupKeys) - popupKeys[labelQuestion] = if (labelQuestion == "?") listOf("¿") else listOf("?", "¿") + popupKeys[labelQuestion] = if (labelQuestion == "?") mutableListOf("¿") else mutableListOf("?", "¿") if ("punctuation" !in popupKeys) - popupKeys["punctuation"] = listOf("${Key.POPUP_KEYS_AUTO_COLUMN_ORDER}8", "\\,", "?", "!", "#", ")", "(", "/", ";", "'", "@", ":", "-", "\"", "+", "\\%", "&") + popupKeys["punctuation"] = mutableListOf("${Key.POPUP_KEYS_AUTO_COLUMN_ORDER}8", "\\,", "?", "!", "#", ")", "(", "/", ";", "'", "@", ":", "-", "\"", "+", "\\%", "&") } - private fun readStream(stream: InputStream?, onlyPopupKeys: Boolean) { + private fun readStream(stream: InputStream?, onlyPopupKeys: Boolean, priority: Boolean) { if (stream == null) return stream.reader().use { reader -> var mode = READER_MODE_NONE @@ -87,7 +88,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { "[number_row]" -> { mode = READER_MODE_NUMBER_ROW; return@forEachLine } } when (mode) { - READER_MODE_POPUP_KEYS -> addPopupKeys(line) + READER_MODE_POPUP_KEYS -> addPopupKeys(line, priority) READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2)) READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2)) READER_MODE_NUMBER_ROW -> setNumberRow(line.splitOnWhitespace(), onlyPopupKeys) @@ -109,19 +110,19 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { fun getShiftSymbolLabel(isTablet: Boolean) = if (isTablet) labelShiftSymbolTablet else labelShiftSymbol - fun getPopupKeys(label: String): List? = popupKeys[label] - fun getPriorityPopupKeys(label: String): List? = priorityPopupKeys[label] + fun getPopupKeys(label: String): Collection? = popupKeys[label] + fun getPriorityPopupKeys(label: String): Collection? = priorityPopupKeys[label] // used by simple parser only, but could be possible for json as well (if necessary) fun getExtraKeys(row: Int): List? = if (row > extraKeys.size) null else extraKeys[row] - fun addFile(dataStream: InputStream?) { - readStream(dataStream, true) + fun addFile(dataStream: InputStream?, priority: Boolean) { + readStream(dataStream, true, priority) } - private fun addPopupKeys(line: String) { + private fun addPopupKeys(line: String, priority: Boolean) { val split = if (line.contains("|")) // if a popup key contains label/code separately, there are cases where space can be in there too // normally this should work for all popup keys, but if we split them on whitespace there is less chance for unnecessary issues @@ -129,26 +130,14 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { else line.splitOnWhitespace() if (split.size == 1) return val key = split.first() - val priorityMarkerIndex = split.indexOf("%") - if (priorityMarkerIndex > 0) { - val existingPriorityPopupKeys = priorityPopupKeys[key] - val newPriorityPopupKeys = split.subList(1, priorityMarkerIndex) - priorityPopupKeys[key] = if (existingPriorityPopupKeys == null) newPriorityPopupKeys - else existingPriorityPopupKeys + newPriorityPopupKeys - val existingPopupKeys = popupKeys[key] - val newPopupKeys = split.subList(priorityMarkerIndex + 1, split.size) - popupKeys[key] = if (existingPopupKeys == null) newPopupKeys - else existingPopupKeys + newPopupKeys - } else { - // a but more special treatment, this should not occur together with priority marker (but technically could) - val existingPopupKeys = popupKeys[key] - val newPopupKeys = if (existingPopupKeys == null) - split.drop(1) - else mergePopupKeys(existingPopupKeys, split.drop(1)) - popupKeys[key] = when (key) { - "'", "\"", "«", "»" -> addFixedColumnOrder(newPopupKeys) - else -> newPopupKeys - } + val popupsMap = if (priority) priorityPopupKeys else popupKeys + if (popupsMap[key] is MutableList) + popupsMap[key] = popupsMap[key]!!.toMutableSet().also { it.addAll(split.drop(1)) } + else if (popupsMap.containsKey(key)) popupsMap[key]!!.addAll(split.drop(1)) + else popupsMap[key] = split.drop(1).toMutableList() // first use a list because usually it's enough + adjustAutoColumnOrder(popupsMap[key]!!) + when (key) { + "'", "\"", "«", "»" -> addFixedColumnOrder(popupsMap[key]!!) } } @@ -219,15 +208,31 @@ private fun mergePopupKeys(original: List, added: List): List): List { - val newPopupKeys = popupKeys.filterNot { it.startsWith(Key.POPUP_KEYS_FIXED_COLUMN_ORDER) } - return listOf("${Key.POPUP_KEYS_FIXED_COLUMN_ORDER}${newPopupKeys.size}") + newPopupKeys +private fun addFixedColumnOrder(popupKeys: MutableCollection) { + // use intermediate list, because we can't add first in a LinkedHashSet (i.e. MutableSet) + popupKeys.removeAll { it.startsWith(Key.POPUP_KEYS_FIXED_COLUMN_ORDER) } + val temp = popupKeys.toList() + popupKeys.clear() + popupKeys.add("${Key.POPUP_KEYS_FIXED_COLUMN_ORDER}${temp.size}") + popupKeys.addAll(temp) +} + +private fun adjustAutoColumnOrder(popupKeys: MutableCollection) { + // same style as above + // currently, POPUP_KEYS_AUTO_COLUMN_ORDER is only used for 2 lines of punctuation popups, so assume 2 lines + if (!popupKeys.removeAll { it.startsWith(Key.POPUP_KEYS_AUTO_COLUMN_ORDER) }) + return + val temp = popupKeys.toList() + popupKeys.clear() + popupKeys.add("${Key.POPUP_KEYS_AUTO_COLUMN_ORDER}${((temp.size + 1) / 2).coerceAtMost(10)}") + popupKeys.addAll(temp) } +// no caching because this might get called first, and thus can mess with the cache +// those 2 ways of creating could be unified, but whatever... fun getOrCreate(context: Context, locale: Locale): LocaleKeyboardInfos = - localeKeyboardInfosCache.getOrPut(locale.toString()) { - LocaleKeyboardInfos(getStreamForLocale(locale, context), locale) - } + localeKeyboardInfosCache[locale.toString()] + ?: LocaleKeyboardInfos(getStreamForLocale(locale, context), locale) fun addLocaleKeyTextsToParams(context: Context, params: KeyboardParams, popupKeysSetting: Int) { val locales = params.mSecondaryLocales + params.mId.locale @@ -238,15 +243,14 @@ fun addLocaleKeyTextsToParams(context: Context, params: KeyboardParams, popupKey private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popupKeysSetting: Int): LocaleKeyboardInfos { val lkt = LocaleKeyboardInfos(getStreamForLocale(params.mId.locale, context), params.mId.locale) - if (popupKeysSetting == POPUP_KEYS_MORE) - lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt")) - else if (popupKeysSetting == POPUP_KEYS_ALL) - lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt")) - else if (popupKeysSetting == POPUP_KEYS_MAIN) - lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_main.txt")) params.mSecondaryLocales.forEach { locale -> if (locale == params.mId.locale) return@forEach - lkt.addFile(getStreamForLocale(locale, context)) + lkt.addFile(getStreamForLocale(locale, context), true) + } + when (popupKeysSetting) { + POPUP_KEYS_MAIN -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_main.txt"), false) + POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false) + POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false) } return lkt } From 544132974ce0c8c4484abe3e82f648a2eab7e7ad Mon Sep 17 00:00:00 2001 From: Devy Ballard <69347329+devycarol@users.noreply.github.com> Date: Fri, 12 Jul 2024 14:35:23 -0600 Subject: [PATCH 91/92] select keys can now de-select text (#973) --- .../java/helium314/keyboard/latin/LatinIME.java | 2 +- .../keyboard/latin/RichInputConnection.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index aecacd5f6..69a722807 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1761,7 +1761,7 @@ public void hapticAndAudioFeedback(final int code, final int repeatCount) { return; break; case KeyCode.ARROW_RIGHT, KeyCode.ARROW_DOWN, KeyCode.WORD_RIGHT, KeyCode.PAGE_DOWN: - if (!mInputLogic.mConnection.canForwardDeleteCharacters()) + if (mInputLogic.mConnection.noTextAfterCursor()) return; break; } diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 151e44804..a5657bf5d 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -363,9 +363,9 @@ public boolean canDeleteCharacters() { return mExpectedSelStart > 0; } - public boolean canForwardDeleteCharacters() { + public boolean noTextAfterCursor() { final CharSequence after = getTextAfterCursor(1, 0); - return !TextUtils.isEmpty(after); + return TextUtils.isEmpty(after); } /** @@ -728,12 +728,17 @@ public boolean setSelection(final int start, final int end) { public void selectAll() { if (!isConnected()) return; - mIC.performContextMenuAction(android.R.id.selectAll); + if (mExpectedSelStart != mExpectedSelEnd && mExpectedSelStart == 0 && noTextAfterCursor()) { // all text already selected + mIC.setSelection(mExpectedSelEnd, mExpectedSelEnd); + } else mIC.performContextMenuAction(android.R.id.selectAll); } public void selectWord(final SpacingAndPunctuations spacingAndPunctuations, final String script) { if (!isConnected()) return; - if (mExpectedSelStart != mExpectedSelEnd) return; // already something selected + if (mExpectedSelStart != mExpectedSelEnd) { // already something selected + mIC.setSelection(mExpectedSelEnd, mExpectedSelEnd); + return; + } final TextRange range = getWordRangeAtCursor(spacingAndPunctuations, script); if (range == null) return; mIC.setSelection(mExpectedSelStart - range.getNumberOfCharsInWordBeforeCursor(), mExpectedSelStart + range.getNumberOfCharsInWordAfterCursor()); From c0c11d75b2889226a3328bbfc90ec439354a76c9 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 13 Jul 2024 09:13:14 +0200 Subject: [PATCH 92/92] add setting to remove redundant popup keys --- app/src/main/assets/locale_key_texts/de.txt | 1 - app/src/main/java/helium314/keyboard/keyboard/Key.java | 2 ++ .../keyboard/keyboard/internal/KeyboardBuilder.kt | 7 ++----- .../latin/settings/PreferencesSettingsFragment.java | 2 +- .../java/helium314/keyboard/latin/settings/Settings.java | 1 + .../helium314/keyboard/latin/settings/SettingsValues.java | 2 ++ app/src/main/res/values/strings.xml | 4 ++++ app/src/main/res/xml/prefs_screen_preferences.xml | 7 +++++++ 8 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/src/main/assets/locale_key_texts/de.txt b/app/src/main/assets/locale_key_texts/de.txt index 965b69389..d51c37f2e 100644 --- a/app/src/main/assets/locale_key_texts/de.txt +++ b/app/src/main/assets/locale_key_texts/de.txt @@ -1,6 +1,5 @@ [popup_keys] a ä -e o ö u ü s ß diff --git a/app/src/main/java/helium314/keyboard/keyboard/Key.java b/app/src/main/java/helium314/keyboard/keyboard/Key.java index 419b50b0c..da9144b61 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/Key.java +++ b/app/src/main/java/helium314/keyboard/keyboard/Key.java @@ -355,6 +355,8 @@ private Key(@NonNull final Key key, @Nullable final PopupKeySpec[] popupKeys) { @NonNull public static Key removeRedundantPopupKeys(@NonNull final Key key, @NonNull final PopupKeySpec.LettersOnBaseLayout lettersOnBaseLayout) { + if ((key.mPopupKeysColumnAndFlags & POPUP_KEYS_FLAGS_FIXED_COLUMN) != 0) + return key; // don't remove anything for fixed column popup keys final PopupKeySpec[] popupKeys = key.getPopupKeys(); final PopupKeySpec[] filteredPopupKeys = PopupKeySpec.removeRedundantPopupKeys( popupKeys, lettersOnBaseLayout); diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt index da14f00bb..627cd036d 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardBuilder.kt @@ -65,14 +65,11 @@ open class KeyboardBuilder(protected val mContext: Context, } private fun setupParams() { - // previously was false for nordic and serbian_qwertz, true for all others - // todo: add setting? maybe users want it in a custom layout - mParams.mAllowRedundantPopupKeys = mParams.mId.mElementId != KeyboardId.ELEMENT_SYMBOLS - + val sv = Settings.getInstance().current + mParams.mAllowRedundantPopupKeys = !sv.mRemoveRedundantPopups mParams.mProximityCharsCorrectionEnabled = mParams.mId.mElementId == KeyboardId.ELEMENT_ALPHABET || (mParams.mId.isAlphabetKeyboard && !mParams.mId.mSubtype.hasExtraValue(Constants.Subtype.ExtraValue.NO_SHIFT_PROXIMITY_CORRECTION)) - val sv = Settings.getInstance().current addLocaleKeyTextsToParams(mContext, mParams, sv.mShowMorePopupKeys) mParams.mPopupKeyTypes.addAll(sv.mPopupKeyTypes) // add label source only if popup key type enabled diff --git a/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java b/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java index a0da00735..914efed62 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/PreferencesSettingsFragment.java @@ -78,7 +78,7 @@ public void onSharedPreferenceChanged(final SharedPreferences prefs, final Strin switch (key) { case Settings.PREF_POPUP_KEYS_ORDER, Settings.PREF_SHOW_POPUP_HINTS, Settings.PREF_SHOW_NUMBER_ROW, Settings.PREF_POPUP_KEYS_LABELS_ORDER, Settings.PREF_LANGUAGE_SWITCH_KEY, - Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY -> mReloadKeyboard = true; + Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY , Settings.PREF_REMOVE_REDUNDANT_POPUPS-> mReloadKeyboard = true; case Settings.PREF_LOCALIZED_NUMBER_ROW -> KeyboardLayoutSet.onSystemLocaleChanged(); case Settings.PREF_SHOW_HINTS -> findPreference(Settings.PREF_POPUP_KEYS_LABELS_ORDER).setVisible(prefs.getBoolean(Settings.PREF_SHOW_HINTS, false)); diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 440e7cb3f..f4c3daeae 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -159,6 +159,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_ABC_AFTER_EMOJI = "abc_after_emoji"; public static final String PREF_ABC_AFTER_CLIP = "abc_after_clip"; public static final String PREF_ABC_AFTER_SYMBOL_SPACE = "abc_after_symbol_space"; + public static final String PREF_REMOVE_REDUNDANT_POPUPS = "remove_redundant_popups"; // Emoji public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index b3c6330c7..a07f87d9d 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -114,6 +114,7 @@ public class SettingsValues { public final boolean mAlphaAfterEmojiInEmojiView; public final boolean mAlphaAfterClipHistoryEntry; public final boolean mAlphaAfterSymbolAndSpace; + public final boolean mRemoveRedundantPopups; // From the input box @NonNull @@ -258,6 +259,7 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mAlphaAfterEmojiInEmojiView = prefs.getBoolean(Settings.PREF_ABC_AFTER_EMOJI, false); mAlphaAfterClipHistoryEntry = prefs.getBoolean(Settings.PREF_ABC_AFTER_CLIP, false); mAlphaAfterSymbolAndSpace = prefs.getBoolean(Settings.PREF_ABC_AFTER_SYMBOL_SPACE, true); + mRemoveRedundantPopups = prefs.getBoolean(Settings.PREF_REMOVE_REDUNDANT_POPUPS, true); } public boolean isApplicationSpecifiedCompletionsOn() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e785dfe42..5fc58d28e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -283,6 +283,10 @@ Show functional hints Show hints if long-pressing a key triggers additional functionality + + Remove redundant popups + + Suppress popup keys that are present on the base layout Change input method with space key diff --git a/app/src/main/res/xml/prefs_screen_preferences.xml b/app/src/main/res/xml/prefs_screen_preferences.xml index c42717ca5..fc2987aa6 100644 --- a/app/src/main/res/xml/prefs_screen_preferences.xml +++ b/app/src/main/res/xml/prefs_screen_preferences.xml @@ -101,6 +101,13 @@ android:defaultValue="false" android:persistent="true" /> + +