add setting for customizing functional key layouts

This commit is contained in:
Helium314 2024-05-22 22:34:17 +02:00
parent 17aa321fbd
commit 0459971e3a
10 changed files with 150 additions and 61 deletions

View file

@ -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"

View file

@ -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<MutableList<KeyData>> {
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

View file

@ -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) {}

View file

@ -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<Preference>("backup_restore")?.setOnPreferenceClickListener { showBackupRestoreDialog() }
findPreference<Preference>("custom_symbols_number_layouts")?.setOnPreferenceClickListener {
showCustomizeLayoutsDialog()
showCustomizeSymbolNumberLayoutsDialog()
true
}
findPreference<Preference>("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

View file

@ -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() {

View file

@ -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<List<Key.KeyParams>>): 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<List<Key.KeyParams>>): 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<File> {
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<String>, 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<File>? = 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."

View file

@ -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<String>()
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

View file

@ -462,6 +462,14 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM
<string name="edit_layout">Tap to edit raw layout</string>
<!-- Title for customizing symbols or number layouts -->
<string name="customize_symbols_number_layouts">Customize symbols and number layouts</string>
<!-- Title for customizing functional key layouts -->
<string name="customize_functional_key_layouts">Customize functional key layouts</string>
<!-- Name for functional keys layout -->
<string name="layout_functional_keys" tools:keep="@string/layout_functional_keys">Functional keys</string>
<!-- Name for functional keys layout when showing symbols layout -->
<string name="layout_functional_keys_symbols" tools:keep="@string/layout_functional_keys_symbols">Functional keys (Symbols)</string>
<!-- Name for functional keys layout when showing more symbols layout -->
<string name="layout_functional_keys_symbols_shifted" tools:keep="@string/layout_functional_keys_symbols_shifted">Functional keys (More symbols)</string>
<!-- Name for symbols layout -->
<string name="layout_symbols" tools:keep="@string/layout_symbols">Symbols</string>
<!-- Name for symbols layout for arabic language -->

View file

@ -90,6 +90,10 @@
android:key="custom_symbols_number_layouts"
android:title="@string/customize_symbols_number_layouts" />
<Preference
android:key="custom_functional_key_layouts"
android:title="@string/customize_functional_key_layouts" />
<Preference
android:key="backup_restore"
android:title="@string/backup_restore_title" />

View file

@ -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)