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 30a5f42f..c492e8d5 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 @@ -197,8 +197,8 @@ 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 (true /* Settings.getInstance().current.mSingleFunctionalLayout */) { // todo with the customizable functional layout - // remove unwanted keys (emoji, numpad, language switch) + 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 } if (!Settings.getInstance().current.isLanguageSwitchKeyEnabled || !params.mId.isAlphabetKeyboard) @@ -314,7 +314,3 @@ const val LAYOUT_NUMPAD_LANDSCAPE = "numpad_landscape" const val LAYOUT_NUMBER = "number" const val LAYOUT_PHONE = "phone" const val LAYOUT_PHONE_SYMBOLS = "phone_symbols" -const val FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED = "functional_keys_symbols_shifted" -const val FUNCTIONAL_LAYOUT_SYMBOLS = "functional_keys_symbols" -const val FUNCTIONAL_LAYOUT = "functional_keys" -const val FUNCTIONAL_LAYOUT_TABLET = "functional_keys_tablet" 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 7c47dae9..1dbb2310 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 @@ -22,8 +22,9 @@ import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX import helium314.keyboard.latin.utils.ScriptUtils import helium314.keyboard.latin.utils.ScriptUtils.script +import helium314.keyboard.latin.utils.getCustomFunctionalLayoutName import helium314.keyboard.latin.utils.getCustomLayoutFile -import helium314.keyboard.latin.utils.getCustomLayoutsDir +import helium314.keyboard.latin.utils.getCustomLayoutFiles import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic @@ -35,13 +36,12 @@ object RawKeyboardParser { val symbolAndNumberLayouts = listOf(LAYOUT_SYMBOLS, LAYOUT_SYMBOLS_SHIFTED, LAYOUT_SYMBOLS_ARABIC, LAYOUT_NUMBER, LAYOUT_NUMPAD, LAYOUT_NUMPAD_LANDSCAPE, LAYOUT_PHONE, LAYOUT_PHONE_SYMBOLS) - // todo: cache is by layout name, this is inefficient for functional keys by default fun clearCache() = rawLayoutCache.clear() fun parseLayout(params: KeyboardParams, context: Context, isFunctional: Boolean = false): MutableList> { val layoutName = if (isFunctional) { if (!params.mId.isAlphaOrSymbolKeyboard) return mutableListOf(mutableListOf()) - else getFunctionalLayoutName(params) + else getFunctionalLayoutName(params, context) } else { getLayoutName(params, context) } @@ -121,43 +121,24 @@ object RawKeyboardParser { else -> params.mId.mSubtype.keyboardLayoutSetName.substringBeforeLast("+") } - // todo (later, see also keyboardParser): use Settings.getInstance().current.mSingleFunctionalLayout - private fun getFunctionalLayoutName(params: KeyboardParams) = when (params.mId.mElementId) { - KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED - KeyboardId.ELEMENT_SYMBOLS -> FUNCTIONAL_LAYOUT_SYMBOLS - else -> if (Settings.getInstance().isTablet) FUNCTIONAL_LAYOUT_TABLET else FUNCTIONAL_LAYOUT + private fun getFunctionalLayoutName(params: KeyboardParams, context: Context): String { + if (Settings.getInstance().current.mHasCustomFunctionalLayout) { + getCustomFunctionalLayoutName(params.mId.mElementId, params.mId.mSubtype.rawSubtype, context) + ?.let { return it } + } + return if (Settings.getInstance().isTablet) "functional_keys_tablet" else "functional_keys" } /** returns the file name matching the layout name, making sure the file exists (falling back to qwerty.txt) */ private fun getLayoutFileName(layoutName: String, context: Context): String { - val customFiles = getCustomLayoutsDir(context).list() + val customFiles = getCustomLayoutFiles(context).map { it.name } if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) { - return if (customFiles?.contains(layoutName) == true) layoutName - else "qwerty.txt" // fallback + return customFiles.firstOrNull { it.startsWith(layoutName)} + ?: if (layoutName.contains("functional")) "functional_keys.json" else "qwerty.txt" // fallback to defaults } val assetsFiles by lazy { context.assets.list("layouts")!! } - if (layoutName.startsWith("functional")) { - // return custom match if we have one - val customMatch = customFiles?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") } - if (customMatch != null) return customMatch - if (layoutName == FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED) { - // no custom symbols shifted layout, try custom symbols layout - val customSymbols = customFiles?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$FUNCTIONAL_LAYOUT_SYMBOLS.") } - if (customSymbols != null) return customSymbols - } - // no custom symbols layout, try custom functional layout - if (Settings.getInstance().isTablet) { - val customTablet = customFiles?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$FUNCTIONAL_LAYOUT_TABLET.") } - if (customTablet != null) return customTablet - } - val customFunctional = customFiles?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$FUNCTIONAL_LAYOUT.") } - if (customFunctional != null) return customFunctional - // no custom functional layout, use the default functional layout - return if (Settings.getInstance().isTablet) "$FUNCTIONAL_LAYOUT_TABLET.json" - else "$FUNCTIONAL_LAYOUT.json" - } return if (layoutName in symbolAndNumberLayouts) { - customFiles?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.")} + customFiles.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.")} ?: assetsFiles.first { it.startsWith(layoutName) } } else { // can't be custom layout, so it must be in assets diff --git a/app/src/main/java/helium314/keyboard/latin/App.kt b/app/src/main/java/helium314/keyboard/latin/App.kt index a97a168a..7932ed2c 100644 --- a/app/src/main/java/helium314/keyboard/latin/App.kt +++ b/app/src/main/java/helium314/keyboard/latin/App.kt @@ -11,7 +11,8 @@ import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.DictionaryInfoUtils -import helium314.keyboard.latin.utils.getCustomLayoutsDir +import helium314.keyboard.latin.utils.getCustomLayoutFile +import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged import helium314.keyboard.latin.utils.upgradeToolbarPrefs import java.io.File @@ -51,10 +52,9 @@ fun checkVersionUpgrade(context: Context) { if (oldVersion == 0) // new install or restoring settings from old app name upgradesWhenComingFromOldAppName(context) if (oldVersion <= 1000) { // upgrade old custom layouts name - val layoutsDir = getCustomLayoutsDir(context) - val oldShiftSymbolsFile = File(layoutsDir, "${CUSTOM_LAYOUT_PREFIX}shift_symbols") + val oldShiftSymbolsFile = getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}shift_symbols", context) if (oldShiftSymbolsFile.exists()) { - oldShiftSymbolsFile.renameTo(File(layoutsDir, "${CUSTOM_LAYOUT_PREFIX}symbols_shifted")) + oldShiftSymbolsFile.renameTo(getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}symbols_shifted", context)) } // rename subtype setting, and clean old subtypes that might remain in some cases @@ -73,6 +73,7 @@ fun checkVersionUpgrade(context: Context) { putString(Settings.PREF_SELECTED_SUBTYPE, selectedSubtype) } } + onCustomLayoutFileListChanged() // just to be sure prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) } } @@ -80,9 +81,8 @@ fun checkVersionUpgrade(context: Context) { private fun upgradesWhenComingFromOldAppName(context: Context) { // move layout files try { - val layoutsDir = getCustomLayoutsDir(context) File(context.filesDir, "layouts").listFiles()?.forEach { - it.copyTo(File(layoutsDir, it.name), true) + it.copyTo(getCustomLayoutFile(it.name, context), true) it.delete() } } catch (_: Exception) {} 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 33751a6a..3669fec4 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt @@ -41,12 +41,15 @@ import helium314.keyboard.latin.common.FileUtils import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy import helium314.keyboard.latin.utils.AdditionalSubtypeUtils +import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_NORMAL +import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS +import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED 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.editCustomLayout -import helium314.keyboard.latin.utils.getCustomLayoutsDir +import helium314.keyboard.latin.utils.getCustomLayoutFiles import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.infoDialog import helium314.keyboard.latin.utils.reloadEnabledSubtypes @@ -127,7 +130,11 @@ class AdvancedSettingsFragment : SubScreenFragment() { findPreference("backup_restore")?.setOnPreferenceClickListener { showBackupRestoreDialog() } findPreference("custom_symbols_number_layouts")?.setOnPreferenceClickListener { - showCustomizeLayoutsDialog() + showCustomizeSymbolNumberLayoutsDialog() + true + } + findPreference("custom_functional_key_layouts")?.setOnPreferenceClickListener { + showCustomizeFunctionalKeyLayoutsDialog() true } } @@ -145,7 +152,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { } } - private fun showCustomizeLayoutsDialog() { + private fun showCustomizeSymbolNumberLayoutsDialog() { val layoutNames = RawKeyboardParser.symbolAndNumberLayouts.map { it.getStringResourceOrName("layout_", requireContext()) }.toTypedArray() AlertDialog.Builder(requireContext()) .setTitle(R.string.customize_symbols_number_layouts) @@ -158,8 +165,8 @@ class AdvancedSettingsFragment : SubScreenFragment() { } private fun customizeSymbolNumberLayout(layoutName: String) { - val customLayoutName = getCustomLayoutsDir(requireContext()).list() - ?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") } + val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name } + .firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") } val originalLayout = if (customLayoutName != null) null else { requireContext().assets.list("layouts")?.firstOrNull { it.startsWith("$layoutName.") } @@ -169,6 +176,32 @@ class AdvancedSettingsFragment : SubScreenFragment() { editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layoutName.txt", requireContext(), originalLayout, displayName) } + private fun showCustomizeFunctionalKeyLayoutsDialog() { + val list = listOf(CUSTOM_FUNCTIONAL_LAYOUT_NORMAL, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS, CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED) + .map { it.substringBeforeLast(".") } + val layoutNames = list.map { it.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) }.toTypedArray() + AlertDialog.Builder(requireContext()) + .setTitle(R.string.customize_functional_key_layouts) + .setItems(layoutNames) { di, i -> + di.dismiss() + customizeFunctionalKeysLayout(list[i]) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + private fun customizeFunctionalKeysLayout(layoutName: String) { + val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name } + .firstOrNull { it.startsWith("$layoutName.") } + val originalLayout = if (customLayoutName != null) null + else { + val defaultLayoutName = if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json" + requireContext().assets.open("layouts" + File.separator + defaultLayoutName).reader().readText() + } + val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) + editCustomLayout(customLayoutName ?: "$layoutName.json", requireContext(), originalLayout, displayName) + } + @SuppressLint("ApplySharedPref") private fun onClickLoadLibrary(): Boolean { // get architecture for telling user which file to use 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 f60df971..92c523bf 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -25,6 +25,7 @@ import helium314.keyboard.latin.R; import helium314.keyboard.latin.RichInputMethodManager; import helium314.keyboard.latin.common.Colors; import helium314.keyboard.latin.permissions.PermissionsUtil; +import helium314.keyboard.latin.utils.CustomLayoutUtilsKt; import helium314.keyboard.latin.utils.InputTypeUtils; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.PopupKeysUtilsKt; @@ -122,6 +123,7 @@ public class SettingsValues { public final SettingsValuesForSuggestion mSettingsValuesForSuggestion; public final boolean mIncognitoModeEnabled; public final boolean mLongPressSymbolsForNumpad; + public final boolean mHasCustomFunctionalLayout; // User-defined colors public final Colors mColors; @@ -237,6 +239,7 @@ public class SettingsValues { 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); + mHasCustomFunctionalLayout = CustomLayoutUtilsKt.hasCustomFunctionalLayout(selectedSubtype, context); } public boolean isApplicationSpecifiedCompletionsOn() { 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 3fa3d2e6..68bc7710 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/CustomLayoutUtils.kt @@ -5,6 +5,7 @@ import android.content.Context import android.net.Uri import android.provider.OpenableColumns import android.text.InputType +import android.view.inputmethod.InputMethodSubtype import android.widget.EditText import androidx.appcompat.app.AlertDialog import androidx.core.widget.doAfterTextChanged @@ -17,6 +18,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL import helium314.keyboard.keyboard.internal.keyboard_parser.RawKeyboardParser import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams import helium314.keyboard.latin.R +import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.FileUtils import java.io.File import java.io.IOException @@ -112,7 +114,7 @@ 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) > 6) } }) { + if (keys.any { row -> row.any { ((it.mLabel?.length ?: 0) > 20) } }) { Log.w(TAG, "too long text on key") return false } @@ -127,10 +129,24 @@ private fun checkKeys(keys: List>): Boolean { return true } +/** don't rename or delete the file without calling [onCustomLayoutFileListChanged] */ fun getCustomLayoutFile(layoutName: String, context: Context) = File(getCustomLayoutsDir(context), layoutName) -fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts") +// cache to avoid frequently listing files +/** don't rename or delete files without calling [onCustomLayoutFileListChanged] */ +fun getCustomLayoutFiles(context: Context): List { + customLayouts?.let { return it } + val layouts = getCustomLayoutsDir(context).listFiles()?.toList() ?: emptyList() + customLayouts = layouts + return layouts +} + +fun onCustomLayoutFileListChanged() { + customLayouts = null +} + +private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts") // undo the name changes in loadCustomLayout when clicking ok fun getLayoutDisplayName(layoutName: String) = @@ -164,6 +180,7 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String? file.writeText(content) if (isJson != wasJson) // unlikely to be needed, but better be safe file.renameTo(File(file.absolutePath.substringBeforeLast(".") + "." + if (isJson) "json" else "txt")) + onCustomLayoutFileListChanged() KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context) } } @@ -173,6 +190,7 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String? builder.setNeutralButton(R.string.delete) { _, _ -> confirmDialog(context, context.getString(R.string.delete_layout, displayName), context.getString(R.string.delete)) { file.delete() + onCustomLayoutFileListChanged() KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context) } } @@ -182,6 +200,45 @@ fun editCustomLayout(layoutName: String, context: Context, startContent: String? builder.show() } +fun hasCustomFunctionalLayout(subtype: InputMethodSubtype, context: Context): Boolean { + val anyCustomFunctionalLayout = getCustomFunctionalLayoutName(KeyboardId.ELEMENT_ALPHABET, subtype, context) + ?: getCustomFunctionalLayoutName(KeyboardId.ELEMENT_SYMBOLS, subtype, context) + ?: getCustomFunctionalLayoutName(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, subtype, context) + return anyCustomFunctionalLayout != null +} + +fun getCustomFunctionalLayoutName(elementId: Int, subtype: InputMethodSubtype, context: Context): String? { + val customFunctionalLayoutNames = getCustomLayoutFiles(context).filter { it.name.contains("functional") }.map { it.name.substringBeforeLast(".") + "." } + if (customFunctionalLayoutNames.isEmpty()) return null + val languageTag = subtype.locale().toLanguageTag() + val mainLayoutName = subtype.getExtraValueOf(Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET) ?: "qwerty" + + if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) { + findMatchingLayout(customFunctionalLayoutNames.filter { it.startsWith(CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED) }, mainLayoutName, languageTag) + ?.let { return it } + } + if (elementId == KeyboardId.ELEMENT_SYMBOLS) { + findMatchingLayout(customFunctionalLayoutNames.filter { it.startsWith(CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS) }, mainLayoutName, languageTag) + ?.let { return it } + } + return findMatchingLayout(customFunctionalLayoutNames.filter { it.startsWith(CUSTOM_FUNCTIONAL_LAYOUT_NORMAL) }, mainLayoutName, languageTag) +} + +// todo (when adding custom layouts per locale or main layout): adjust mainLayoutName for custom layouts? +// remove language tag and file ending (currently name is e.g. custom.en-US.abcdfdsg3.json, and we could use abcdfdsg3 only) +// this way, custom layouts with same name could use same custom functional layouts +// currently there is no way to set the language tag or main layout name, so changes don't break backwards compatibility +private fun findMatchingLayout(layoutNames: List, mainLayoutName: String, languageTag: String): String? { + // first find layout with matching locale and main layout + return layoutNames.firstOrNull { it.endsWith(".$languageTag.$mainLayoutName.") } + // then find matching main layout + ?: layoutNames.firstOrNull { it.endsWith(".$mainLayoutName.") } + // then find matching language + ?: layoutNames.firstOrNull { it.endsWith(".$languageTag.") } + // then find "normal" functional layout (make use of the '.' separator + ?: layoutNames.firstOrNull { it.count { it == '.' } == 2 } +} + private fun encodeBase36(string: String): String = BigInteger(string.toByteArray()).toString(36) private fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray().decodeToString() @@ -189,3 +246,8 @@ private fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray(). // this goes into prefs and file names, so do not change! const val CUSTOM_LAYOUT_PREFIX = "custom." private const val TAG = "CustomLayoutUtils" +private var customLayouts: List? = null + +const val CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED = "${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols_shifted." +const val CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS = "${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols." +const val CUSTOM_FUNCTIONAL_LAYOUT_NORMAL = "${CUSTOM_LAYOUT_PREFIX}functional_keys." 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 a57f1a4e..ccd79c7c 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt @@ -245,12 +245,12 @@ private fun loadResourceSubtypes(resources: Resources) { private fun removeInvalidCustomSubtypes(context: Context) { val prefs = DeviceProtectedUtils.getSharedPreferences(context) val additionalSubtypes = Settings.readPrefAdditionalSubtypes(prefs, context.resources).split(";") - val customSubtypeFiles by lazy { getCustomLayoutsDir(context).list() } + val customSubtypeFiles by lazy { getCustomLayoutFiles(context).map { it.name } } val subtypesToRemove = mutableListOf() additionalSubtypes.forEach { val name = it.substringAfter(":").substringBefore(":") if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach - if (customSubtypeFiles?.contains(name) != true) + if (name !in customSubtypeFiles) subtypesToRemove.add(it) } if (subtypesToRemove.isEmpty()) return diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 54b9163d..f8dcbb9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -462,6 +462,14 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM Tap to edit raw layout Customize symbols and number layouts + + Customize functional key layouts + + Functional keys + + Functional keys (Symbols) + + Functional keys (More symbols) Symbols diff --git a/app/src/main/res/xml/prefs_screen_advanced.xml b/app/src/main/res/xml/prefs_screen_advanced.xml index 061104f6..80422afb 100644 --- a/app/src/main/res/xml/prefs_screen_advanced.xml +++ b/app/src/main/res/xml/prefs_screen_advanced.xml @@ -90,6 +90,10 @@ android:key="custom_symbols_number_layouts" android:title="@string/customize_symbols_number_layouts" /> + + diff --git a/layouts.md b/layouts.md index fed2c015..13dd05dc 100644 --- a/layouts.md +++ b/layouts.md @@ -117,13 +117,15 @@ You can also specify special key codes like `a|!code/key_action_previous`, but i * If a newly added language does not use latin script, please update the default scripts method `Locale.script` in [ScriptUtils](app/src/main/java/helium314/keyboard/latin/utils/ScriptUtils.kt) ## Functional key layouts -This is not yet customizable, but will be soon! -Mostly customizing functional keys works like other layouts, with some specific adjustments: -* you can either have a single layout for functional keys (default), or separate layouts for symbols and shift symbols - * when using a single layout - * emoji and language switch keys will only show in alphabet layout and when the option is enabled - * numpad key will only show in symbols layout - * otherwise the layout will be shown as it is in the layout file +Customizing functional keys mostly works like other layouts, with some specific adjustments: +* When using the default functional layout, emoji, language switch and numpad keys are actually always in the layout, but get removed depending on settings and the main layout (alphabet, symbols or more symbols). This removal is disabled when you customize any functional layout, so to not block you from adding e.g. a numpad key in alphabet layout. +* When you use a language that has a ZWNJ key, the key will automatically be added to the right of the (first) space bar in the bottom row +* Adding popups to keys that switch layout does not work properly, as usually the layout is switched as soon as the key gets pressed. * use keys with `"type": "placeholder"` for * separating left and right functional keys (e.g. shift and delete in default layout) - * separating top and bottom rows in case you want to have functional key rows aligned to the top of the keyboard + * separating top and bottom rows in case you want to have functional key rows aligned to the top of the keyboard (add a row with the placeholder as the only key) +* if the last row in functional keys does not contain a placeholder, it is used as bottom row (like in the default functional layout) +* When you functional keys only for some of alphabet, symbols and more symbols, behavior is as follows + * more symbols will fall back to symbols, then normal + * symbols will fall back to normal, then default (if you only customized more symbols functional layout) + * normal will fall back to default (if you only customized symbols and/or more symbols functional layout)