mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-03 13:14:46 +00:00
overhaul style of parsing layouts
and add a cache
This commit is contained in:
parent
1bd30f612b
commit
ed776c3804
12 changed files with 256 additions and 262 deletions
|
@ -17,6 +17,7 @@ import helium314.keyboard.keyboard.internal.KeyboardParams;
|
|||
import helium314.keyboard.keyboard.internal.UniqueKeysCache;
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LocaleKeyboardInfos;
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LocaleKeyboardInfosKt;
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.RawKeyboardParser;
|
||||
import helium314.keyboard.latin.RichInputMethodSubtype;
|
||||
import helium314.keyboard.latin.utils.InputTypeUtils;
|
||||
import helium314.keyboard.latin.utils.Log;
|
||||
|
@ -99,6 +100,7 @@ public final class KeyboardLayoutSet {
|
|||
private static void clearKeyboardCache() {
|
||||
sKeyboardCache.clear();
|
||||
sUniqueKeysCache.clear();
|
||||
RawKeyboardParser.INSTANCE.clearCache();
|
||||
}
|
||||
|
||||
KeyboardLayoutSet(final Context context, @NonNull final Params params) {
|
||||
|
|
|
@ -59,7 +59,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
mParams.mPopupKeyTypes.addAll(sv.mPopupKeyTypes)
|
||||
// add label source only if popup key type enabled
|
||||
sv.mPopupKeyLabelSources.forEach { if (it in sv.mPopupKeyTypes) mParams.mPopupKeyLabelSources.add(it) }
|
||||
keysInRows = KeyboardParser.parseLayout(mParams, mContext)
|
||||
keysInRows = KeyboardParser(mParams, mContext).parseLayout()
|
||||
determineAbsoluteValues()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "error parsing layout $id ${id.mElementId}", e)
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.keyboard.internal.keyboard_parser
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.AbstractKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.AutoTextKeyData
|
||||
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.LayoutDirectionSelector
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.ShiftStateSelector
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.TextKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSelector
|
||||
|
||||
/**
|
||||
* Parser for json layout files as used in FlorisBoard, see floris directory for classes taken from FlorisBoard.
|
||||
* Some differences to the FlorisBoard keys:
|
||||
* (currently) only normal keys supported
|
||||
* if label or code are missing one is created from the other
|
||||
* auto_text_key ignored (i.e. interpreted like the default TextKey)
|
||||
* codes of multi_text_key not used, only the label
|
||||
* (currently) popups is always read to [number, main, relevant] layoutPopupKeys, no choice of which to use or which hint is provided
|
||||
*/
|
||||
class JsonKeyboardParser(private val params: KeyboardParams, context: Context) : KeyboardParser(params, context) {
|
||||
|
||||
override fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>> {
|
||||
val florisKeyData: List<List<AbstractKeyData>> = florisJsonConfig.decodeFromString(layoutContent)
|
||||
// initially 200 ms parse (debug build on S4 mini)
|
||||
// after a few parses it's optimized and 20-30 ms
|
||||
// whole load is 50-70 ms vs 30-55 with simple parser -> it's ok
|
||||
return florisKeyData.mapTo(mutableListOf()) { it.mapNotNull { it.compute(params) } }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
private val florisJsonConfig = Json {
|
||||
classDiscriminator = "$"
|
||||
encodeDefaults = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
serializersModule = SerializersModule {
|
||||
polymorphic(AbstractKeyData::class) {
|
||||
subclass(TextKeyData::class, TextKeyData.serializer())
|
||||
subclass(AutoTextKeyData::class, AutoTextKeyData.serializer())
|
||||
subclass(MultiTextKeyData::class, MultiTextKeyData.serializer())
|
||||
subclass(CaseSelector::class, CaseSelector.serializer())
|
||||
subclass(ShiftStateSelector::class, ShiftStateSelector.serializer())
|
||||
subclass(VariationSelector::class, VariationSelector.serializer())
|
||||
subclass(LayoutDirectionSelector::class, LayoutDirectionSelector.serializer())
|
||||
subclass(CharWidthSelector::class, CharWidthSelector.serializer())
|
||||
subclass(KanaSelector::class, KanaSelector.serializer())
|
||||
defaultDeserializer { TextKeyData.serializer() }
|
||||
}
|
||||
polymorphic(KeyData::class) {
|
||||
subclass(TextKeyData::class, TextKeyData.serializer())
|
||||
subclass(AutoTextKeyData::class, AutoTextKeyData.serializer())
|
||||
subclass(MultiTextKeyData::class, MultiTextKeyData.serializer())
|
||||
defaultDeserializer { TextKeyData.serializer() }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,19 +24,16 @@ import helium314.keyboard.latin.common.isEmoji
|
|||
import helium314.keyboard.latin.define.DebugFlags
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
import helium314.keyboard.latin.spellcheck.AndroidSpellCheckerService
|
||||
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
|
||||
import helium314.keyboard.latin.utils.InputTypeUtils
|
||||
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.getLayoutFile
|
||||
import helium314.keyboard.latin.utils.removeFirst
|
||||
import helium314.keyboard.latin.utils.replaceFirst
|
||||
import helium314.keyboard.latin.utils.runInLocale
|
||||
import helium314.keyboard.latin.utils.splitAt
|
||||
import helium314.keyboard.latin.utils.sumOf
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Abstract parser class that handles creation of keyboard from [KeyData] arranged in rows,
|
||||
|
@ -47,7 +44,7 @@ import java.io.File
|
|||
* By default, all normal keys have the same width and flags, which may cause issues with the
|
||||
* requirements of certain non-latin languages.
|
||||
*/
|
||||
abstract class KeyboardParser(private val params: KeyboardParams, private val context: Context) {
|
||||
class KeyboardParser(private val params: KeyboardParams, private val context: Context) {
|
||||
private val infos = layoutInfos(params)
|
||||
private val defaultLabelFlags = when {
|
||||
params.mId.isAlphabetKeyboard -> params.mLocaleKeyboardInfos.labelFlags
|
||||
|
@ -57,10 +54,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
else -> 0
|
||||
}
|
||||
|
||||
abstract fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>>
|
||||
|
||||
// this thing does too much... make it more understandable after everything is implemented
|
||||
fun parseLayoutString(layoutContent: String): ArrayList<ArrayList<KeyParams>> {
|
||||
fun parseLayout(): ArrayList<ArrayList<KeyParams>> {
|
||||
params.readAttributes(context, null)
|
||||
params.mProximityCharsCorrectionEnabled = infos.enableProximityCharsCorrection
|
||||
if (infos.touchPositionCorrectionData == null) // need to set correctly, as it's not properly done in readAttributes with attr = null
|
||||
|
@ -68,7 +62,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
else
|
||||
params.mTouchPositionCorrection.load(context.resources.getStringArray(infos.touchPositionCorrectionData))
|
||||
|
||||
val baseKeys = parseCoreLayout(layoutContent)
|
||||
val baseKeys = RawKeyboardParser.parseLayout(params, context)
|
||||
val keysInRows = createRows(baseKeys)
|
||||
// rescale height if we have anything but the usual 4 rows
|
||||
val heightRescale = if (keysInRows.size != 4) 4f / keysInRows.size else 1f
|
||||
|
@ -79,26 +73,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
return keysInRows
|
||||
}
|
||||
|
||||
// this should be ready for customizable functional layouts, but needs cleanup
|
||||
// todo (later): remove this as part of adding a cache for parsed layouts
|
||||
private fun getFunctionalKeyLayoutText(): String {
|
||||
if (params.mId.isNumberLayout) return "[]" // empty list
|
||||
val layouts = Settings.getLayoutsDir(context).list() ?: emptyArray()
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||
if ("functional_keys_symbols_shifted.json" in layouts)
|
||||
return getLayoutFile("functional_keys_symbols_shifted.json", context).readText()
|
||||
}
|
||||
if (!params.mId.isAlphabetKeyboard) {
|
||||
if ("functional_keys_symbols.json" in layouts)
|
||||
return getLayoutFile("functional_keys_symbols.json", context).readText()
|
||||
}
|
||||
if ("functional_keys.json" in layouts)
|
||||
return getLayoutFile("functional_keys.json", context).readText()
|
||||
val fileName = if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json"
|
||||
return context.readAssetsLayoutFile(fileName)
|
||||
}
|
||||
|
||||
private fun createRows(baseKeys: MutableList<List<KeyData>>): ArrayList<ArrayList<KeyParams>> {
|
||||
private fun createRows(baseKeys: MutableList<MutableList<KeyData>>): ArrayList<ArrayList<KeyParams>> {
|
||||
// add padding for number layouts in landscape mode (maybe do it some other way later)
|
||||
if (params.mId.isNumberLayout && params.mId.mElementId != KeyboardId.ELEMENT_NUMPAD
|
||||
&& context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
|
@ -112,14 +87,14 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
addSymbolPopupKeys(baseKeys)
|
||||
if (params.mId.isAlphaOrSymbolKeyboard && params.mId.mNumberRowEnabled)
|
||||
baseKeys.add(0, params.mLocaleKeyboardInfos.getNumberRow()
|
||||
.map { it.copy(newLabelFlags = Key.LABEL_FLAGS_DISABLE_HINT_LABEL or defaultLabelFlags) })
|
||||
.mapTo(mutableListOf()) { it.copy(newLabelFlags = Key.LABEL_FLAGS_DISABLE_HINT_LABEL or defaultLabelFlags) })
|
||||
|
||||
val allFunctionalKeys = JsonKeyboardParser(params, context).parseCoreLayout(getFunctionalKeyLayoutText())
|
||||
val allFunctionalKeys = RawKeyboardParser.parseLayout(params, context, true)
|
||||
adjustBottomFunctionalRowAndBaseKeys(allFunctionalKeys, baseKeys)
|
||||
|
||||
if (allFunctionalKeys.none { it.singleOrNull()?.isKeyPlaceholder() == true })
|
||||
// add a placeholder so splitAt does what we really want
|
||||
allFunctionalKeys.add(0, listOf(TextKeyData(type = KeyType.PLACEHOLDER)))
|
||||
allFunctionalKeys.add(0, mutableListOf(TextKeyData(type = KeyType.PLACEHOLDER)))
|
||||
|
||||
val (functionalKeysTop, functionalKeysBottom) = allFunctionalKeys.splitAt { it.singleOrNull()?.isKeyPlaceholder() == true }
|
||||
|
||||
|
@ -235,8 +210,8 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
* does nothing if not isAlphaOrSymbolKeyboard or assumptions not met
|
||||
* adds an empty row to baseKeys, to have a baseKey row for the bottom functional row
|
||||
*/
|
||||
private fun adjustBottomFunctionalRowAndBaseKeys(allFunctionalKeys: MutableList<List<KeyData>>, baseKeys: MutableList<List<KeyData>>) {
|
||||
val functionalKeysBottom = allFunctionalKeys.lastOrNull()?.toMutableList() ?: return
|
||||
private fun adjustBottomFunctionalRowAndBaseKeys(allFunctionalKeys: MutableList<MutableList<KeyData>>, baseKeys: MutableList<MutableList<KeyData>>) {
|
||||
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
|
||||
|
@ -287,8 +262,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
functionalKeysBottom.add(spaceIndex, key1)
|
||||
}
|
||||
}
|
||||
allFunctionalKeys[allFunctionalKeys.lastIndex] = functionalKeysBottom
|
||||
baseKeys.add(emptyList())
|
||||
baseKeys.add(mutableListOf())
|
||||
}
|
||||
|
||||
// ideally we would get all functional keys in a nice list of pairs from the start, but at least it works...
|
||||
|
@ -331,12 +305,12 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
}
|
||||
|
||||
private fun addNumberRowOrPopupKeys(baseKeys: MutableList<List<KeyData>>) {
|
||||
private fun addNumberRowOrPopupKeys(baseKeys: MutableList<MutableList<KeyData>>) {
|
||||
if (!params.mId.mNumberRowEnabled && params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||
// replace first symbols row with number row, but use the labels as popupKeys
|
||||
val numberRow = params.mLocaleKeyboardInfos.getNumberRow()
|
||||
numberRow.forEachIndexed { index, keyData -> keyData.popup.symbol = baseKeys[0].getOrNull(index)?.label }
|
||||
baseKeys[0] = numberRow
|
||||
baseKeys[0] = numberRow.toMutableList()
|
||||
} else if (!params.mId.mNumberRowEnabled && params.mId.isAlphabetKeyboard && infos.numbersOnTopRow) {
|
||||
if (baseKeys[0].any { it.popup.main != null || !it.popup.relevant.isNullOrEmpty() } // first row of baseKeys has any layout popup key
|
||||
&& params.mPopupKeyLabelSources.let {
|
||||
|
@ -356,15 +330,9 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
}
|
||||
|
||||
private fun addSymbolPopupKeys(baseKeys: MutableList<List<KeyData>>) {
|
||||
val layoutName = getLayoutFileName(params, context, overrideElementId = KeyboardId.ELEMENT_SYMBOLS)
|
||||
val layout = if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||
else SimpleKeyboardParser(params, context, false)
|
||||
parser.parseCoreLayout(getLayoutFile(layoutName, context).readText())
|
||||
} else {
|
||||
SimpleKeyboardParser(params, context, false).parseCoreLayout(context.readAssetsLayoutFile("$layoutName.txt"))
|
||||
}
|
||||
private fun addSymbolPopupKeys(baseKeys: MutableList<MutableList<KeyData>>) {
|
||||
val layoutName = if (params.mId.locale.script() == ScriptUtils.SCRIPT_ARABIC) LAYOUT_SYMBOLS_ARABIC else LAYOUT_SYMBOLS
|
||||
val layout = RawKeyboardParser.parseLayout(layoutName, params, context)
|
||||
layout.forEachIndexed { i, row ->
|
||||
val baseRow = baseKeys.getOrNull(i) ?: return@forEachIndexed
|
||||
row.forEachIndexed { j, key ->
|
||||
|
@ -492,49 +460,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
companion object {
|
||||
private const val TAG = "KeyboardParser"
|
||||
|
||||
// todo: this is somewhat awkward and could be re-organized
|
||||
// simple and json parser should just parse the core layout
|
||||
// adding extra keys should be done in KeyboardParser
|
||||
fun parseLayout(params: KeyboardParams, context: Context): ArrayList<ArrayList<KeyParams>> {
|
||||
val layoutName = getLayoutFileName(params, context)
|
||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||
val parser = if (layoutName.endsWith("json")) JsonKeyboardParser(params, context)
|
||||
else SimpleKeyboardParser(params, context)
|
||||
return parser.parseLayoutString(getLayoutFile(layoutName, context).readText())
|
||||
}
|
||||
val layoutFileNames = context.assets.list("layouts")!!
|
||||
if (layoutFileNames.contains("$layoutName.json")) {
|
||||
return JsonKeyboardParser(params, context).parseLayoutString(context.readAssetsLayoutFile("$layoutName.json"))
|
||||
}
|
||||
if (layoutFileNames.contains("$layoutName.txt")) {
|
||||
return SimpleKeyboardParser(params, context).parseLayoutString(context.readAssetsLayoutFile("$layoutName.txt"))
|
||||
}
|
||||
throw IllegalStateException("can't parse layout $layoutName with id ${params.mId} and elementId ${params.mId.mElementId}")
|
||||
}
|
||||
|
||||
private fun Context.readAssetsLayoutFile(name: String) = assets.open("layouts${File.separator}$name").reader().readText()
|
||||
|
||||
private fun getLayoutFileName(params: KeyboardParams, context: Context, overrideElementId: Int? = null): String {
|
||||
var checkForCustom = true
|
||||
val layoutName = when (overrideElementId ?: params.mId.mElementId) {
|
||||
KeyboardId.ELEMENT_SYMBOLS -> if (params.mId.locale.script() == ScriptUtils.SCRIPT_ARABIC) LAYOUT_SYMBOLS_ARABIC else LAYOUT_SYMBOLS
|
||||
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> LAYOUT_SYMBOLS_SHIFTED
|
||||
KeyboardId.ELEMENT_NUMPAD -> if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
LAYOUT_NUMPAD_LANDSCAPE
|
||||
else
|
||||
LAYOUT_NUMPAD
|
||||
KeyboardId.ELEMENT_NUMBER -> LAYOUT_NUMBER
|
||||
KeyboardId.ELEMENT_PHONE -> LAYOUT_PHONE
|
||||
KeyboardId.ELEMENT_PHONE_SYMBOLS -> LAYOUT_PHONE_SYMBOLS
|
||||
else -> {
|
||||
checkForCustom = false // "custom" is already in keyboardLayoutSetName
|
||||
params.mId.mSubtype.keyboardLayoutSetName.substringBeforeLast("+")
|
||||
}
|
||||
}
|
||||
return if (checkForCustom) Settings.readLayoutName(layoutName, context)
|
||||
else layoutName
|
||||
}
|
||||
|
||||
// todo:
|
||||
// layoutInfos should be stored in method.xml (imeSubtypeExtraValue)
|
||||
// or somewhere else... some replacement for keyboard_layout_set xml maybe
|
||||
|
@ -619,3 +544,7 @@ 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"
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.keyboard.internal.keyboard_parser
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.AbstractKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.AutoTextKeyData
|
||||
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.LayoutDirectionSelector
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.ShiftStateSelector
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.TextKeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSelector
|
||||
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.ScriptUtils
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import helium314.keyboard.latin.utils.getCustomLayoutFile
|
||||
import helium314.keyboard.latin.utils.getCustomLayoutsDir
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import java.io.File
|
||||
|
||||
object RawKeyboardParser {
|
||||
private val rawLayoutCache = hashMapOf<String, (KeyboardParams) -> MutableList<MutableList<KeyData>>>()
|
||||
|
||||
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 {
|
||||
getLayoutName(params, context)
|
||||
}
|
||||
return rawLayoutCache.getOrPut(layoutName) {
|
||||
createCacheLambda(layoutName, context)
|
||||
}(params)
|
||||
}
|
||||
|
||||
fun parseLayout(layoutName: String, params: KeyboardParams, context: Context): MutableList<MutableList<KeyData>> {
|
||||
return rawLayoutCache.getOrPut(layoutName) {
|
||||
createCacheLambda(layoutName, context)
|
||||
}(params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse for json layout files as used in FlorisBoard, see floris directory for classes taken from FlorisBoard.
|
||||
* Some differences to the FlorisBoard keys:
|
||||
* (currently) only normal keys supported
|
||||
* if label or code are missing one is created from the other
|
||||
* auto_text_key ignored (i.e. interpreted like the default TextKey)
|
||||
* codes of multi_text_key not used, only the label
|
||||
* (currently) popups is always read to [number, main, relevant] layoutPopupKeys, no choice of which to use or which hint is provided
|
||||
*/
|
||||
fun parseJsonString(layoutText: String): List<List<AbstractKeyData>> = florisJsonConfig.decodeFromString(layoutText)
|
||||
|
||||
/** Parse simple layouts, defined only as rows of (normal) keys with popup keys. */
|
||||
fun parseSimpleString(layoutText: String): List<List<KeyData>> {
|
||||
val rowStrings = layoutText.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex()).filter { it.isNotBlank() }
|
||||
return rowStrings.map { row ->
|
||||
row.split("\n").mapNotNull { parseKey(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseKey(key: String): KeyData? {
|
||||
if (key.isBlank()) return null
|
||||
val split = key.splitOnWhitespace()
|
||||
return if (split.size == 1) split.first().toTextKey()
|
||||
else split.first().toTextKey(split.drop(1))
|
||||
}
|
||||
|
||||
private fun createCacheLambda(layoutName: String, context: Context): (KeyboardParams) -> MutableList<MutableList<KeyData>> {
|
||||
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() }
|
||||
if (layoutFileName.endsWith(".json")) {
|
||||
val florisKeyData = parseJsonString(layoutText)
|
||||
return { params ->
|
||||
florisKeyData.mapTo(mutableListOf()) { row ->
|
||||
row.mapNotNullTo(mutableListOf()) { it.compute(params) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val simpleKeyData = parseSimpleString(layoutText)
|
||||
return { params ->
|
||||
simpleKeyData.mapIndexedTo(mutableListOf()) { i, row ->
|
||||
val newRow = row.toMutableList()
|
||||
val extraKeys = params.mId.mSubtype.keyboardLayoutSetName.endsWith("+") && params.mId.isAlphabetKeyboard
|
||||
if (extraKeys)
|
||||
params.mLocaleKeyboardInfos.getExtraKeys(i+1)?.let { newRow.addAll(it) }
|
||||
println("${newRow.size}: ${newRow.map { it.label }}")
|
||||
newRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLayoutName(params: KeyboardParams, context: Context) = when (params.mId.mElementId) {
|
||||
KeyboardId.ELEMENT_SYMBOLS -> if (params.mId.locale.script() == ScriptUtils.SCRIPT_ARABIC) LAYOUT_SYMBOLS_ARABIC else LAYOUT_SYMBOLS
|
||||
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> LAYOUT_SYMBOLS_SHIFTED
|
||||
KeyboardId.ELEMENT_NUMPAD -> if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
LAYOUT_NUMPAD_LANDSCAPE
|
||||
else
|
||||
LAYOUT_NUMPAD
|
||||
KeyboardId.ELEMENT_NUMBER -> LAYOUT_NUMBER
|
||||
KeyboardId.ELEMENT_PHONE -> LAYOUT_PHONE
|
||||
KeyboardId.ELEMENT_PHONE_SYMBOLS -> LAYOUT_PHONE_SYMBOLS
|
||||
else -> params.mId.mSubtype.keyboardLayoutSetName.substringBeforeLast("+")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/** 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()
|
||||
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) {
|
||||
return if (customFiles?.contains(layoutName) == true) layoutName
|
||||
else "qwerty.txt" // fallback
|
||||
}
|
||||
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.")}
|
||||
?: assetsFiles.first { it.startsWith(layoutName) }
|
||||
} else {
|
||||
// can't be custom layout, so it must be in assets
|
||||
val searchName = layoutName.substringBeforeLast("+") // consider there are layouts ending in "+" for adding extra keys
|
||||
assetsFiles.firstOrNull { it.startsWith(searchName) } ?: "qwerty.txt" // in case it was removed
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
private val florisJsonConfig = Json {
|
||||
classDiscriminator = "$"
|
||||
encodeDefaults = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
serializersModule = SerializersModule {
|
||||
polymorphic(AbstractKeyData::class) {
|
||||
subclass(TextKeyData::class, TextKeyData.serializer())
|
||||
subclass(AutoTextKeyData::class, AutoTextKeyData.serializer())
|
||||
subclass(MultiTextKeyData::class, MultiTextKeyData.serializer())
|
||||
subclass(CaseSelector::class, CaseSelector.serializer())
|
||||
subclass(ShiftStateSelector::class, ShiftStateSelector.serializer())
|
||||
subclass(VariationSelector::class, VariationSelector.serializer())
|
||||
subclass(LayoutDirectionSelector::class, LayoutDirectionSelector.serializer())
|
||||
subclass(CharWidthSelector::class, CharWidthSelector.serializer())
|
||||
subclass(KanaSelector::class, KanaSelector.serializer())
|
||||
defaultDeserializer { TextKeyData.serializer() }
|
||||
}
|
||||
polymorphic(KeyData::class) {
|
||||
subclass(TextKeyData::class, TextKeyData.serializer())
|
||||
subclass(AutoTextKeyData::class, AutoTextKeyData.serializer())
|
||||
subclass(MultiTextKeyData::class, MultiTextKeyData.serializer())
|
||||
defaultDeserializer { TextKeyData.serializer() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package helium314.keyboard.keyboard.internal.keyboard_parser
|
||||
|
||||
import android.content.Context
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey
|
||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||
|
||||
/**
|
||||
* Parser for simple layouts, defined only as rows of (normal) keys with popup keys.
|
||||
* There may be a short "extra row" for the configurable keys in the bottom row. This is two keys
|
||||
* for alphabet, 3 keys for symbols and 4 keys for shift symbols. Popup keys on period and comma get
|
||||
* merged with defaults.
|
||||
*/
|
||||
class SimpleKeyboardParser(
|
||||
private val params: KeyboardParams,
|
||||
context: Context,
|
||||
private val addExtraKeys: Boolean = params.mId.mSubtype.keyboardLayoutSetName.endsWith("+") && params.mId.isAlphabetKeyboard
|
||||
) : KeyboardParser(params, context) {
|
||||
override fun parseCoreLayout(layoutContent: String): MutableList<List<KeyData>> {
|
||||
val rowStrings = layoutContent.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex())
|
||||
return rowStrings.mapIndexedNotNullTo(mutableListOf()) { i, row ->
|
||||
if (row.isBlank()) return@mapIndexedNotNullTo null
|
||||
if (addExtraKeys)
|
||||
getExtraKeys(i)?.let { parseRow(row) + it } ?: parseRow(row)
|
||||
else
|
||||
parseRow(row)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseRow(row: String): List<KeyData> =
|
||||
row.split("\n").mapNotNull {
|
||||
if (it.isBlank()) null
|
||||
else parseKey(it)
|
||||
}
|
||||
|
||||
private fun getExtraKeys(rowIndex: Int) = params.mLocaleKeyboardInfos.getExtraKeys(rowIndex + 1)
|
||||
|
||||
private fun parseKey(key: String): KeyData {
|
||||
val split = key.splitOnWhitespace()
|
||||
return if (split.size == 1)
|
||||
split.first().toTextKey()
|
||||
else
|
||||
split.first().toTextKey(split.drop(1))
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@ 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.upgradeToolbarPrefs
|
||||
import java.io.File
|
||||
|
||||
|
@ -50,7 +51,7 @@ 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 = Settings.getLayoutsDir(context)
|
||||
val layoutsDir = getCustomLayoutsDir(context)
|
||||
val oldShiftSymbolsFile = File(layoutsDir, "${CUSTOM_LAYOUT_PREFIX}shift_symbols")
|
||||
if (oldShiftSymbolsFile.exists()) {
|
||||
oldShiftSymbolsFile.renameTo(File(layoutsDir, "${CUSTOM_LAYOUT_PREFIX}symbols_shifted"))
|
||||
|
@ -79,7 +80,7 @@ fun checkVersionUpgrade(context: Context) {
|
|||
private fun upgradesWhenComingFromOldAppName(context: Context) {
|
||||
// move layout files
|
||||
try {
|
||||
val layoutsDir = Settings.getLayoutsDir(context)
|
||||
val layoutsDir = getCustomLayoutsDir(context)
|
||||
File(context.filesDir, "layouts").listFiles()?.forEach {
|
||||
it.copyTo(File(layoutsDir, it.name), true)
|
||||
it.delete()
|
||||
|
|
|
@ -32,6 +32,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_PHONE_SYMBOLS
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_ARABIC
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.LAYOUT_SYMBOLS_SHIFTED
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.RawKeyboardParser
|
||||
import helium314.keyboard.latin.AudioAndHapticFeedbackManager
|
||||
import helium314.keyboard.latin.BuildConfig
|
||||
import helium314.keyboard.latin.R
|
||||
|
@ -46,6 +47,7 @@ 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.getStringResourceOrName
|
||||
import helium314.keyboard.latin.utils.infoDialog
|
||||
import helium314.keyboard.latin.utils.reloadEnabledSubtypes
|
||||
|
@ -157,27 +159,27 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
|||
}
|
||||
|
||||
private fun showCustomizeLayoutsDialog() {
|
||||
val layouts = listOf(LAYOUT_SYMBOLS, LAYOUT_SYMBOLS_SHIFTED, LAYOUT_SYMBOLS_ARABIC, LAYOUT_NUMBER, LAYOUT_NUMPAD, LAYOUT_NUMPAD_LANDSCAPE, LAYOUT_PHONE, LAYOUT_PHONE_SYMBOLS)
|
||||
val layoutNames = layouts.map { it.getStringResourceOrName("layout_", requireContext()) }.toTypedArray()
|
||||
val layoutNames = RawKeyboardParser.symbolAndNumberLayouts.map { it.getStringResourceOrName("layout_", requireContext()) }.toTypedArray()
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.customize_symbols_number_layouts)
|
||||
.setItems(layoutNames) { di, i ->
|
||||
di.dismiss()
|
||||
customizeLayout(layouts[i])
|
||||
customizeSymbolNumberLayout(RawKeyboardParser.symbolAndNumberLayouts[i])
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun customizeLayout(layout: String) {
|
||||
val customLayoutName = Settings.readLayoutName(layout, context).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) }
|
||||
private fun customizeSymbolNumberLayout(layoutName: String) {
|
||||
val customLayoutName = getCustomLayoutsDir(requireContext()).list()
|
||||
?.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") }
|
||||
val originalLayout = if (customLayoutName != null) null
|
||||
else {
|
||||
requireContext().assets.list("layouts")?.firstOrNull { it.startsWith("$layout.") }
|
||||
requireContext().assets.list("layouts")?.firstOrNull { it.startsWith("$layoutName.") }
|
||||
?.let { requireContext().assets.open("layouts" + File.separator + it).reader().readText() }
|
||||
}
|
||||
val displayName = layout.getStringResourceOrName("layout_", requireContext())
|
||||
editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layout.txt", requireContext(), originalLayout, displayName)
|
||||
val displayName = layoutName.getStringResourceOrName("layout_", requireContext())
|
||||
editCustomLayout(customLayoutName ?: "$CUSTOM_LAYOUT_PREFIX$layoutName.txt", requireContext(), originalLayout, displayName)
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
|
|
|
@ -36,7 +36,6 @@ import helium314.keyboard.latin.common.Colors;
|
|||
import helium314.keyboard.latin.common.LocaleUtils;
|
||||
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils;
|
||||
import helium314.keyboard.latin.utils.ColorUtilKt;
|
||||
import helium314.keyboard.latin.utils.CustomLayoutUtilsKt;
|
||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils;
|
||||
import helium314.keyboard.latin.utils.JniUtils;
|
||||
import helium314.keyboard.latin.utils.Log;
|
||||
|
@ -548,22 +547,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
};
|
||||
}
|
||||
|
||||
/** @return custom layout name if there is one for the given layout, else returns "layout" */
|
||||
public static String readLayoutName(final String layout, final Context context) {
|
||||
String[] layouts = getLayoutsDir(context).list();
|
||||
if (layouts != null) {
|
||||
for (String name : layouts) {
|
||||
if (name.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX + layout + "."))
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
public static File getLayoutsDir(final Context context) {
|
||||
return new File(DeviceProtectedUtils.getFilesDir(context), "layouts");
|
||||
}
|
||||
|
||||
@Nullable public static Drawable readUserBackgroundImage(final Context context, final boolean night) {
|
||||
if (night && sCachedBackgroundNight != null) return sCachedBackgroundNight;
|
||||
if (!night && sCachedBackgroundDay != null) return sCachedBackgroundDay;
|
||||
|
|
|
@ -13,9 +13,8 @@ import helium314.keyboard.keyboard.KeyboardId
|
|||
import helium314.keyboard.keyboard.KeyboardLayoutSet
|
||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.JsonKeyboardParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.SimpleKeyboardParser
|
||||
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.FileUtils
|
||||
|
@ -65,7 +64,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: Str
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
// name must be encoded to avoid issues with validity of subtype extra string or file name
|
||||
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}.${if (isJson) "json" else "txt"}"
|
||||
val file = getLayoutFile(name, context)
|
||||
val file = getCustomLayoutFile(name, context)
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
file.parentFile?.mkdir()
|
||||
|
@ -81,21 +80,21 @@ private fun checkLayout(layoutContent: String, context: Context): Boolean? {
|
|||
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
|
||||
addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL)
|
||||
try {
|
||||
val keys = JsonKeyboardParser(params, context).parseLayoutString(layoutContent)
|
||||
val keys = RawKeyboardParser.parseJsonString(layoutContent).map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } }
|
||||
if (!checkKeys(keys))
|
||||
return null
|
||||
return true
|
||||
} catch (e: Exception) { Log.w(TAG, "error parsing custom json layout", e) }
|
||||
try {
|
||||
val keys = SimpleKeyboardParser(params, context).parseLayoutString(layoutContent)
|
||||
val keys = RawKeyboardParser.parseSimpleString(layoutContent).map { it.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.startsWith("[")) {
|
||||
// layout can't be loaded, assume it's json -> try json layout again because of error message readout
|
||||
// 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 {
|
||||
JsonKeyboardParser(params, context).parseLayoutString(layoutContent)
|
||||
RawKeyboardParser.parseJsonString(layoutContent).map { it.mapNotNull { it.compute(params)?.toKeyParams(params) } }
|
||||
} catch (e: Exception) { Log.w(TAG, "error parsing custom json layout", e) }
|
||||
}
|
||||
return null
|
||||
|
@ -129,8 +128,10 @@ private fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
|
|||
return true
|
||||
}
|
||||
|
||||
fun getLayoutFile(layoutName: String, context: Context) =
|
||||
File(Settings.getLayoutsDir(context), layoutName)
|
||||
fun getCustomLayoutFile(layoutName: String, context: Context) =
|
||||
File(getCustomLayoutsDir(context), layoutName)
|
||||
|
||||
fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
|
||||
|
||||
// undo the name changes in loadCustomLayout when clicking ok
|
||||
fun getLayoutDisplayName(layoutName: String) =
|
||||
|
@ -141,11 +142,11 @@ fun getLayoutDisplayName(layoutName: String) =
|
|||
}
|
||||
|
||||
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
||||
getLayoutFile(layoutName, context).delete()
|
||||
getCustomLayoutFile(layoutName, context).delete()
|
||||
}
|
||||
|
||||
fun editCustomLayout(layoutName: String, context: Context, startContent: String? = null, displayName: CharSequence? = null) {
|
||||
val file = getLayoutFile(layoutName, context)
|
||||
val file = getCustomLayoutFile(layoutName, context)
|
||||
val editText = EditText(context).apply {
|
||||
setText(startContent ?: file.readText())
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ 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 { Settings.getLayoutsDir(context).list() }
|
||||
val customSubtypeFiles by lazy { getCustomLayoutsDir(context).list() }
|
||||
val subtypesToRemove = mutableListOf<String>()
|
||||
additionalSubtypes.forEach {
|
||||
val name = it.substringAfter(":").substringBefore(":")
|
||||
|
|
|
@ -12,13 +12,10 @@ import helium314.keyboard.keyboard.internal.KeyboardBuilder
|
|||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.TouchPositionCorrection
|
||||
import helium314.keyboard.keyboard.internal.UniqueKeysCache
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.JsonKeyboardParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.SimpleKeyboardParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.RawKeyboardParser
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyType
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.MultiTextKeyData
|
||||
import helium314.keyboard.latin.LatinIME
|
||||
import helium314.keyboard.latin.RichInputMethodSubtype
|
||||
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype
|
||||
|
@ -120,7 +117,7 @@ f""", // no newline at the end
|
|||
val wantedKeyLabels = listOf(listOf("a", "b", "c"), listOf("d", "e", "f"))
|
||||
layoutStrings.forEachIndexed { i, layout ->
|
||||
println(i)
|
||||
val keyLabels = SimpleKeyboardParser(params, latinIME).parseCoreLayout(layout).map { it.map { it.label } }
|
||||
val keyLabels = RawKeyboardParser.parseSimpleString(layout).map { it.map { it.toKeyParams(params).mLabel } }
|
||||
assertEquals(wantedKeyLabels, keyLabels)
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +265,7 @@ f""", // no newline at the end
|
|||
]
|
||||
]
|
||||
""".trimIndent()
|
||||
val keys = JsonKeyboardParser(params, latinIME).parseCoreLayout(layoutString)
|
||||
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)
|
||||
|
@ -298,13 +295,13 @@ f""", // no newline at the end
|
|||
val editorInfo = EditorInfo()
|
||||
val subtype = createEmojiCapableAdditionalSubtype(Locale.GERMANY, "qwertz+", true)
|
||||
val (kb, keys) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET)
|
||||
assertEquals(keys[0].size, 11)
|
||||
assertEquals(keys[1].size, 11)
|
||||
assertEquals(keys[2].size, 10)
|
||||
assertEquals(11, keys[0].size)
|
||||
assertEquals(11, keys[1].size)
|
||||
assertEquals(10, keys[2].size)
|
||||
val (kb2, keys2) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)
|
||||
assertEquals(keys2[0].size, 11)
|
||||
assertEquals(keys2[1].size, 11)
|
||||
assertEquals(keys2[2].size, 10)
|
||||
assertEquals(11, keys2[0].size)
|
||||
assertEquals(11, keys2[1].size)
|
||||
assertEquals(10, keys2[2].size)
|
||||
}
|
||||
|
||||
@Test fun `popup key count does not depend on shift for (for simple layout)`() {
|
||||
|
|
Loading…
Add table
Reference in a new issue