Skip to content

Commit

Permalink
perf: dynamically load and cache keyboard layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nopdan committed Feb 6, 2024
1 parent 48da4e4 commit 8123c25
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 124 deletions.
50 changes: 9 additions & 41 deletions app/src/main/java/com/osfans/trime/data/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import androidx.core.math.MathUtils
import com.osfans.trime.core.Rime
import com.osfans.trime.data.AppPrefs
import com.osfans.trime.data.DataManager.userDataDir
import com.osfans.trime.data.schema.SchemaManager
import com.osfans.trime.data.sound.SoundThemeManager
import com.osfans.trime.ime.keyboard.Key
import com.osfans.trime.util.CollectionUtils
Expand All @@ -43,6 +42,7 @@ class Theme(private var isDarkMode: Boolean) {
private var presetColorSchemes: Map<String, Map<String, Any>?>? = null
private var presetKeyboards: Map<String, Any?>? = null
private var liquidKeyboard: Map<String, Any?>? = null
lateinit var allKeyboardIds: List<String>

// 当前配色 id
lateinit var currentColorSchemeId: String
Expand Down Expand Up @@ -109,10 +109,16 @@ class Theme(private var isDarkMode: Boolean) {
presetColorSchemes = fullThemeConfigMap!!["preset_color_schemes"] as Map<String, Map<String, Any>?>?
presetKeyboards = fullThemeConfigMap!!["preset_keyboards"] as Map<String, Any?>?
liquidKeyboard = fullThemeConfigMap!!["liquid_keyboard"] as Map<String, Any?>?
}.also { Timber.d("Setting up all theme config map takes $it ms") }
// 将 presetKeyboards 的所有 key 转为 allKeyboardIds
allKeyboardIds = presetKeyboards!!.keys.toList()
}.also {
Timber.d("Setting up all theme config map takes $it ms")
}
measureTimeMillis {
initColorScheme()
}.also { Timber.d("Initializing cache takes $it ms") }
}.also {
Timber.d("Initializing cache takes $it ms")
}
Timber.i("The theme is initialized")
}.getOrElse {
Timber.e("Failed to parse the theme: ${it.message}")
Expand Down Expand Up @@ -245,44 +251,6 @@ class Theme(private var isDarkMode: Boolean) {
fun getObject(key: String): Any? {
return CollectionUtils.obtainValue(theme.presetKeyboards, key)
}

fun remapKeyboardId(name: String): String {
val remapped =
if (".default" == name) {
val currentSchemaId = Rime.getCurrentRimeSchema()
if (theme.presetKeyboards!!.containsKey(currentSchemaId)) {
return currentSchemaId
} else {
val alphabet = SchemaManager.getActiveSchema().alphabet
val twentySix = "qwerty"
if (!alphabet.isNullOrEmpty() && theme.presetKeyboards!!.containsKey(alphabet)) {
return alphabet
} else {
if (!alphabet.isNullOrEmpty() && (alphabet.contains(",") || alphabet.contains(";"))) {
twentySix + "_"
} else if (!alphabet.isNullOrEmpty() && (alphabet.contains("0") || alphabet.contains("1"))) {
twentySix + "0"
} else {
twentySix
}
}
}
} else {
name
}
if (!theme.presetKeyboards!!.containsKey(remapped)) {
Timber.w("Cannot find keyboard definition %s, fallback ...", remapped)
val defaultMap =
theme.presetKeyboards!!["default"] as Map<String, Any>?
?: throw IllegalStateException("The default keyboard definition is missing!")
return if (defaultMap.containsKey("import_preset")) {
defaultMap["import_preset"] as? String ?: "default"
} else {
"default"
}
}
return remapped
}
}

/**
Expand Down
38 changes: 29 additions & 9 deletions app/src/main/java/com/osfans/trime/ime/keyboard/Keyboard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,26 @@ class Keyboard() {
height = y + keyHeight
}

constructor(name: String?) : this() {
private fun getKeyboardConfig(name: String): Map<String, Any?>? {
val theme = ThemeManager.activeTheme
val keyboardConfig: Map<String, Any?>?
val v = theme.keyboards.getObject(name!!)
keyboardConfig =
val v = theme.keyboards.getObject(name)
val keyboardConfig =
if (v != null) {
v as Map<String, Any?>?
} else {
theme.keyboards.getObject("default") as Map<String, Any?>?
}
val importPreset = keyboardConfig?.get("import_preset") as String?
if (importPreset != null) {
return getKeyboardConfig(importPreset)
}
return keyboardConfig
}

constructor(name: String) : this() {
val theme = ThemeManager.activeTheme
val keyboardConfig = getKeyboardConfig(name)

mLabelTransform = obtainString(keyboardConfig, "label_transform", "none")
mAsciiMode = obtainInt(keyboardConfig, "ascii_mode", 1)
if (mAsciiMode == 0) asciiKeyboard = obtainString(keyboardConfig, "ascii_keyboard", "")
Expand Down Expand Up @@ -195,18 +205,24 @@ class Keyboard() {
horizontalGap =
sp2px(
obtainFloat(
keyboardConfig, "horizontal_gap", theme.style.getFloat("horizontal_gap"),
keyboardConfig,
"horizontal_gap",
theme.style.getFloat("horizontal_gap"),
),
).toInt()
verticalGap =
sp2px(
obtainFloat(
keyboardConfig, "vertical_gap", theme.style.getFloat("vertical_gap"),
keyboardConfig,
"vertical_gap",
theme.style.getFloat("vertical_gap"),
),
).toInt()
roundCorner =
obtainFloat(
keyboardConfig, "round_corner", theme.style.getFloat("round_corner"),
keyboardConfig,
"round_corner",
theme.style.getFloat("round_corner"),
)
val horizontalGap = horizontalGap
val verticalGap = verticalGap
Expand Down Expand Up @@ -361,13 +377,17 @@ class Keyboard() {
key.key_symbol_offset_x =
sp2px(
obtainFloat(
mk, "key_symbol_offset_x", defaultKeySymbolOffsetX.toFloat(),
mk,
"key_symbol_offset_x",
defaultKeySymbolOffsetX.toFloat(),
),
).toInt()
key.key_symbol_offset_y =
sp2px(
obtainFloat(
mk, "key_symbol_offset_y", defaultKeySymbolOffsetY.toFloat(),
mk,
"key_symbol_offset_y",
defaultKeySymbolOffsetY.toFloat(),
),
).toInt()
key.key_hint_offset_x =
Expand Down
167 changes: 93 additions & 74 deletions app/src/main/java/com/osfans/trime/ime/keyboard/KeyboardSwitcher.kt
Original file line number Diff line number Diff line change
@@ -1,130 +1,149 @@
package com.osfans.trime.ime.keyboard

import android.content.res.Configuration
import com.osfans.trime.core.Rime
import com.osfans.trime.data.AppPrefs
import com.osfans.trime.data.schema.SchemaManager
import com.osfans.trime.data.theme.ThemeManager
import com.osfans.trime.util.appContext
import timber.log.Timber

/** Manages [Keyboard]s and their status. **/
object KeyboardSwitcher {
private var currentId: Int = 0
private var lastId: Int = 0
private var lastLockId: Int = 0

private val theme get() = ThemeManager.activeTheme
private val allKeyboardIds get() = theme.allKeyboardIds
private val keyboardCache: MutableMap<String, Keyboard> = mutableMapOf()
private var currentKeyboardId = ".default"
private var lastKeyboardId = ".default"
private var lastLockKeyboardId = ".default"
private var currentDisplayWidth: Int = 0
private val keyboardPrefs = KeyboardPrefs()

private val theme get() = ThemeManager.activeTheme
private lateinit var availableKeyboardIds: List<String>
private lateinit var availableKeyboards: List<Keyboard>
lateinit var currentKeyboard: Keyboard

/** To get current keyboard instance. **/
@JvmStatic
val currentKeyboard: Keyboard
get() {
if (currentId >= availableKeyboards.size) currentId = 0
return availableKeyboards[currentId]
private fun getKeyboard(name: String): Keyboard {
if (keyboardCache.containsKey(name)) {
return keyboardCache[name]!!
}
val keyboard = Keyboard(name)
keyboardCache[name] = keyboard
return keyboard
}

init {
newOrReset()
}

@Suppress("UNCHECKED_CAST")
@JvmStatic
fun newOrReset() {
Timber.d("Switching keyboard back to .default ...")
availableKeyboardIds = (theme.style.getObject("keyboards") as? List<String>)
?.map { theme.keyboards.remapKeyboardId(it) }?.distinct() ?: listOf()

availableKeyboards =
availableKeyboardIds.map {
try {
Keyboard(theme.keyboards.remapKeyboardId(it))
} catch (e: Exception) {
Keyboard("default")
}
}

currentId = 0
lastId = 0
lastLockId = 0
currentKeyboardId = ".default"
lastKeyboardId = ".default"
lastLockKeyboardId = ".default"
currentDisplayWidth = 0
keyboardCache.clear()
switchKeyboard(currentKeyboardId)
}

fun switchKeyboard(name: String?) {
val idx =
val currentIdx = theme.allKeyboardIds.indexOf(currentKeyboardId)
var mappedName =
when (name) {
".default" -> 0
".prior" -> currentId - 1
".next" -> currentId + 1
".last" -> lastId
".last_lock" -> lastLockId
".default" -> autoMatch(name)
".prior" ->
try {
theme.allKeyboardIds[currentIdx - 1]
} catch (e: IndexOutOfBoundsException) {
currentKeyboardId
}
".next" ->
try {
theme.allKeyboardIds[currentIdx + 1]
} catch (e: IndexOutOfBoundsException) {
currentKeyboardId
}
".last" -> lastKeyboardId
".last_lock" -> lastLockKeyboardId
".ascii" -> {
val asciiKeyboard = availableKeyboards[currentId].asciiKeyboard
if (asciiKeyboard.isNullOrEmpty()) {
currentId
val asciiKeyboard = currentKeyboard.asciiKeyboard
if (asciiKeyboard != null && asciiKeyboard in allKeyboardIds) {
asciiKeyboard
} else {
availableKeyboardIds.indexOf(asciiKeyboard)
currentKeyboardId
}
}
else -> {
if (name.isNullOrEmpty()) {
if (availableKeyboards[currentId].isLock) currentId else lastLockId
if (currentKeyboard.isLock) currentKeyboardId else lastLockKeyboardId
} else {
availableKeyboardIds.indexOf(name)
name
}
}
}
val i =
if (keyboardPrefs.isLandscapeMode()) {
mapToLandscapeKeyboardIdx(idx)
} else {
idx
}

Timber.d("Mapped from %d to %d", idx, i)

// 切换到 mini 键盘
val deviceKeyboard = appContext.resources.configuration.keyboard
if (currentId >= 0 && availableKeyboards[currentId].isLock) {
lastLockId = currentId
if (AppPrefs.defaultInstance().theme.useMiniKeyboard && deviceKeyboard != Configuration.KEYBOARD_NOKEYS) {
if ("mini" in allKeyboardIds) mappedName = "mini"
}
lastId = currentId

currentId = if (i >= availableKeyboardIds.size || i < 0) 0 else i
if ("mini" in availableKeyboardIds) {
val mini = availableKeyboardIds.indexOf("mini")
currentId =
if (AppPrefs.defaultInstance().theme.useMiniKeyboard && deviceKeyboard != Configuration.KEYBOARD_NOKEYS) {
if (currentId == 0) mini else currentId
} else {
if (currentId == mini) 0 else currentId
}
// 切换到横屏布局
if (keyboardPrefs.isLandscapeMode()) {
val landscapeKeyboard = getKeyboard(mappedName).landscapeKeyboard
if (landscapeKeyboard != null && landscapeKeyboard in allKeyboardIds) {
mappedName = landscapeKeyboard
}
}

// 应用键盘布局
Timber.i(
"Switched keyboard from ${availableKeyboardIds[lastId]} " +
"to ${availableKeyboardIds[currentId]} (deviceKeyboard=$deviceKeyboard).",
"Switched keyboard from $currentKeyboardId " +
"to $mappedName (deviceKeyboard=$deviceKeyboard).",
)
currentKeyboardId = mappedName
currentKeyboard = getKeyboard(currentKeyboardId)

if (currentKeyboard.isLock) {
lastLockKeyboardId = currentKeyboardId
}
lastKeyboardId = currentKeyboardId
}

private fun mapToLandscapeKeyboardIdx(idx: Int): Int {
if (idx < 0 || idx > availableKeyboards.size) return idx
val landscapeKeyboard = availableKeyboards[idx].landscapeKeyboard
return if (landscapeKeyboard.isNullOrBlank()) {
idx
} else {
availableKeyboardIds.indexOf(landscapeKeyboard)
/**
* .default 自动匹配键盘布局
* */
private fun autoMatch(name: String): String {
// 主题的布局中包含方案id,直接采用
val currentSchemaId = Rime.getCurrentRimeSchema()
if (currentSchemaId in allKeyboardIds) {
return currentSchemaId
}
// 获取方案中的 alphabet(包含所有用到的按键
val alphabet = SchemaManager.getActiveSchema().alphabet
if (alphabet.isNullOrEmpty()) return "default"

val layout =
if (alphabet.matches(Regex("^[a-z]+$"))) {
// 包含 26 个字母
"qwerty"
} else if (alphabet.matches(Regex("^[a-z,./;]+$"))) {
// 包含 26 个字母和,./;
"qwerty_"
} else if (alphabet.matches(Regex("^[a-z0-9]+$"))) {
// 包含 26 个字母和数字键
"qwerty0"
} else {
null
}
if (layout != null && layout in allKeyboardIds) return layout

Timber.d("Could not find keyboard layout $layout, fallback to default")
return "default"
}

/**
* Change current display width when e.g. rotate the screen.
*/
fun resize(displayWidth: Int) {
if (currentId >= 0 && (displayWidth == currentDisplayWidth)) return

if (displayWidth == currentDisplayWidth) return
currentDisplayWidth = displayWidth
newOrReset()
}
Expand Down

0 comments on commit 8123c25

Please sign in to comment.