From 74f6ad3ed6173426754a83690f2d3e2f8dbc1a57 Mon Sep 17 00:00:00 2001 From: limuzi013 <128580527+limuzi013@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:28:39 +0800 Subject: [PATCH] Release 0.0.9-beta.1: docked full-width landscape default, per-orientation keyboard height, split nine-key number hand Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 9 + VERSION | 2 +- .../AuditInteractionInstrumentedTest.kt | 2 +- .../slacker/openime/ImeSettingsActivity.kt | 6 +- .../slacker/openime/LocalVoiceImeService.kt | 2 +- .../slacker/openime/core/LandscapeLayout.kt | 12 +- .../openime/data/ImeSettingsRepository.kt | 25 ++- .../slacker/openime/data/UserDataArchive.kt | 13 ++ .../openime/keyboard/ImeKeyboardView.kt | 27 ++- .../keyboard/Pinyin9KeyboardRenderer.kt | 194 +++++++++--------- .../openime/panel/SettingsPanelController.kt | 5 +- .../slacker/openime/theme/ImeDesignTokens.kt | 6 +- docs/COMPATIBILITY.md | 2 +- scripts/design_reference_e2e.py | 4 +- 14 files changed, 185 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7efd1fcd..7ee4cc9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ GitHub Release 的发布说明;版本与发布流程见 [docs/RELEASE.md](docs ## [Unreleased] +## [0.0.9-beta.1] - 2026-10-10 + +第九个测试版:横屏默认改回铺满宽度的停靠键盘,竖屏和横屏各记一份键盘高度,横屏左右分离的九键改成“一手数字、一手拼音”。从 0.0.8-beta.1 覆盖安装即可,用户数据保留。 + +- 横屏默认沿用竖屏的停靠键盘布局并铺满可用宽度,避免新装后自动缩成居中浮窗;浮动和左右分离仍可在偏好设置中选择。已经选过横屏布局的用户不受影响。 +- 竖屏和横屏分别记忆键盘高度,偏好设置里的滑块按当前方向显示“竖屏键盘高度”或“横屏键盘高度”;横屏的整宽、浮动、左右分离布局共用横屏高度,旧版高度设置会作为两种方向的初始值。数据备份和恢复包含横屏高度。 +- 横屏按键行从 40dp 加高到 48dp,横屏顶栏从 48dp 加高到 54dp,按键更大更好按。 +- 横屏左右分离的九键:九宫格保持熟悉的 3×3 顺序,另一只手改为直接输入数字的 1–9、0 和逗号,加上符号列和“互换”键;去掉原来的常用字面板和中间的“镜像”键。 + ## [0.0.8-beta.1] - 2026-10-09 第八个测试版:英文大写手势、长按后滑动选字、横屏浮动与左右分离键盘、工具栏的复制和验证码提示,修复九键拼音栏缺音节和排序错误、一次点按两次反馈、分段选词学不到。从 0.0.7-beta.1 覆盖安装即可,用户数据保留。 diff --git a/VERSION b/VERSION index d3fe5be0..336681e2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.8-beta.1 +0.0.9-beta.1 diff --git a/app/src/androidTest/java/llc/slacker/openime/AuditInteractionInstrumentedTest.kt b/app/src/androidTest/java/llc/slacker/openime/AuditInteractionInstrumentedTest.kt index 70686c99..433937b5 100644 --- a/app/src/androidTest/java/llc/slacker/openime/AuditInteractionInstrumentedTest.kt +++ b/app/src/androidTest/java/llc/slacker/openime/AuditInteractionInstrumentedTest.kt @@ -449,7 +449,7 @@ class AuditInteractionInstrumentedTest { harness.awaitMain { keyboard.showPanel(Panel.SETTINGS) val settings = keyboard.findViewWithTag("settings-panel") - listOf("键盘高度", "浮动宽度", "浮动透明度").forEach { label -> + listOf("竖屏键盘高度", "浮动宽度", "浮动透明度").forEach { label -> val slider = settings.findViewWithTag("settings-slider:$label") assertTrue("$label must keep a 48dp touch target", slider.minimumHeight >= keyboard.resources.displayMetrics.density * 48f) assertTrue("$label must expose its current value", slider.contentDescription.toString().contains(label)) diff --git a/app/src/main/java/llc/slacker/openime/ImeSettingsActivity.kt b/app/src/main/java/llc/slacker/openime/ImeSettingsActivity.kt index fefabdfd..bc542259 100644 --- a/app/src/main/java/llc/slacker/openime/ImeSettingsActivity.kt +++ b/app/src/main/java/llc/slacker/openime/ImeSettingsActivity.kt @@ -214,7 +214,11 @@ class ImeSettingsActivity : Activity(), ImeKeyboardView.Listener { refreshLiveIme() } override fun onKeyboardHeightChanged(percent: Int) { - ImeSettingsRepository.saveKeyboardHeightPercent(this, percent) + ImeSettingsRepository.saveKeyboardHeightPercent( + this, + percent, + landscape = resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE, + ) refreshLiveIme() } override fun onFloatingStyleChanged(widthPercent: Int, opacityPercent: Int) { diff --git a/app/src/main/java/llc/slacker/openime/LocalVoiceImeService.kt b/app/src/main/java/llc/slacker/openime/LocalVoiceImeService.kt index 093f97c2..5eeac624 100644 --- a/app/src/main/java/llc/slacker/openime/LocalVoiceImeService.kt +++ b/app/src/main/java/llc/slacker/openime/LocalVoiceImeService.kt @@ -335,7 +335,7 @@ class LocalVoiceImeService : InputMethodService(), ImeKeyboardView.Listener, Can resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE /** - * Landscape layout setting: floating (default), full width, or split halves. + * Landscape layout setting: full width (default), floating, or split halves. * Only the transitions the setting itself caused are undone: a keyboard the * user floated from the tools page stays floating in both orientations. */ diff --git a/app/src/main/java/llc/slacker/openime/core/LandscapeLayout.kt b/app/src/main/java/llc/slacker/openime/core/LandscapeLayout.kt index 4d06db06..129dc0e7 100644 --- a/app/src/main/java/llc/slacker/openime/core/LandscapeLayout.kt +++ b/app/src/main/java/llc/slacker/openime/core/LandscapeLayout.kt @@ -3,10 +3,9 @@ package llc.slacker.openime.core /** * How the keyboard sits when the phone is held sideways. * - * WeChat Keyboard floats by itself in landscape and Gboard does the same; the - * Chinese keyboards otherwise stretch the full width. Sogou also offers a split - * keyboard, one half under each thumb. Floating is the default because a - * full-width keyboard of four rows covers most of a landscape screen. + * The default keeps the docked keyboard surface and controls consistent with + * portrait, using the extra width for larger, easier-to-reach keys. Floating + * and split layouts remain optional alternatives for landscape use. */ enum class LandscapeLayout(val key: String, val label: String) { FLOATING("floating", "浮动"), @@ -16,6 +15,9 @@ enum class LandscapeLayout(val key: String, val label: String) { companion object { // "side" was an earlier name of the split layout. fun fromKey(key: String?): LandscapeLayout = - entries.firstOrNull { it.key == key } ?: if (key == "side") SPLIT else FLOATING + entries.firstOrNull { it.key == key } ?: when (key) { + "side" -> SPLIT + else -> FULL + } } } diff --git a/app/src/main/java/llc/slacker/openime/data/ImeSettingsRepository.kt b/app/src/main/java/llc/slacker/openime/data/ImeSettingsRepository.kt index f75fab4e..e61a2b9d 100644 --- a/app/src/main/java/llc/slacker/openime/data/ImeSettingsRepository.kt +++ b/app/src/main/java/llc/slacker/openime/data/ImeSettingsRepository.kt @@ -28,6 +28,7 @@ object ImeSettingsRepository { private const val KEY_VOICE_PUNCTUATION_AS_SPACE = "voice_punctuation_as_space" private const val KEY_PREFERRED_CHINESE_MODE = "preferred_chinese_mode" private const val KEY_KEYBOARD_HEIGHT = "keyboard_height_percent" + private const val KEY_LANDSCAPE_KEYBOARD_HEIGHT = "landscape_keyboard_height_percent" private const val KEY_FLOATING_WIDTH = "floating_width_percent" private const val KEY_FLOATING_OPACITY = "floating_opacity_percent" private const val KEY_LANDSCAPE_LAYOUT = "landscape_layout" @@ -54,14 +55,26 @@ object ImeSettingsRepository { .edit().putString(KEY_PREFERRED_CHINESE_MODE, mode.name).apply() } - fun loadKeyboardHeightPercent(context: Context): Int = - context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - .getInt(KEY_KEYBOARD_HEIGHT, 100) - .coerceIn(80, 120) + fun loadKeyboardHeightPercent(context: Context, landscape: Boolean = false): Int { + val preferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + val stored = if (landscape && preferences.contains(KEY_LANDSCAPE_KEYBOARD_HEIGHT)) { + preferences.getInt(KEY_LANDSCAPE_KEYBOARD_HEIGHT, 100) + } else { + // Existing height preferences seed landscape until a user changes + // its independent value, preserving the current appearance on upgrade. + preferences.getInt(KEY_KEYBOARD_HEIGHT, 100) + } + return stored.coerceIn(80, 120) + } - fun saveKeyboardHeightPercent(context: Context, percent: Int) { + fun saveKeyboardHeightPercent(context: Context, percent: Int, landscape: Boolean = false) { context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - .edit().putInt(KEY_KEYBOARD_HEIGHT, percent.coerceIn(80, 120)).apply() + .edit() + .putInt( + if (landscape) KEY_LANDSCAPE_KEYBOARD_HEIGHT else KEY_KEYBOARD_HEIGHT, + percent.coerceIn(80, 120), + ) + .apply() } fun loadSplitMirrored(context: Context): Boolean = diff --git a/app/src/main/java/llc/slacker/openime/data/UserDataArchive.kt b/app/src/main/java/llc/slacker/openime/data/UserDataArchive.kt index 29806bfa..1d947b17 100644 --- a/app/src/main/java/llc/slacker/openime/data/UserDataArchive.kt +++ b/app/src/main/java/llc/slacker/openime/data/UserDataArchive.kt @@ -50,6 +50,7 @@ internal data class ArchiveSettings( val railSymbols: List? = null, /** Keys of the 模糊音 pairs switched on; null in backups made before the switches. */ val fuzzyRules: List? = null, + val landscapeKeyboardHeightPercent: Int = keyboardHeightPercent, ) internal data class RimeUserDictionaryArchive( @@ -190,6 +191,7 @@ internal object UserDataArchiveCodec { .put("swipe_up_digits", value.swipeUpDigits) .put("preferred_chinese_mode", value.preferredChineseMode) .put("keyboard_height_percent", value.keyboardHeightPercent) + .put("landscape_keyboard_height_percent", value.landscapeKeyboardHeightPercent) .put("floating_width_percent", value.floatingWidthPercent) .put("floating_opacity_percent", value.floatingOpacityPercent) .put("letter_hints", value.letterHints) @@ -217,6 +219,10 @@ internal object UserDataArchiveCodec { ), keyboardHeightPercent = value.optInt("keyboard_height_percent", 100).coerceIn(80, 120), + landscapeKeyboardHeightPercent = value.optInt( + "landscape_keyboard_height_percent", + value.optInt("keyboard_height_percent", 100), + ).coerceIn(80, 120), floatingWidthPercent = value.optInt("floating_width_percent", 88).coerceIn(72, 96), floatingOpacityPercent = @@ -353,6 +359,8 @@ internal object UserDataRepository { ImeSettingsRepository.loadPreferredChineseMode(context).name, keyboardHeightPercent = ImeSettingsRepository.loadKeyboardHeightPercent(context), + landscapeKeyboardHeightPercent = + ImeSettingsRepository.loadKeyboardHeightPercent(context, landscape = true), floatingWidthPercent = ImeSettingsRepository.loadFloatingWidthPercent(context), floatingOpacityPercent = @@ -448,6 +456,11 @@ internal object UserDataRepository { context, value.keyboardHeightPercent, ) + ImeSettingsRepository.saveKeyboardHeightPercent( + context, + value.landscapeKeyboardHeightPercent, + landscape = true, + ) ImeSettingsRepository.saveFloatingWidthPercent( context, value.floatingWidthPercent, diff --git a/app/src/main/java/llc/slacker/openime/keyboard/ImeKeyboardView.kt b/app/src/main/java/llc/slacker/openime/keyboard/ImeKeyboardView.kt index 61c4c468..e5497f6a 100644 --- a/app/src/main/java/llc/slacker/openime/keyboard/ImeKeyboardView.kt +++ b/app/src/main/java/llc/slacker/openime/keyboard/ImeKeyboardView.kt @@ -300,7 +300,10 @@ open class ImeKeyboardView( private var appliedScreenWidthDp = resources.configuration.screenWidthDp private var appliedFontScale = resources.configuration.fontScale private var appliedDensityDpi = resources.displayMetrics.densityDpi - private var keyboardHeightPercent = ImeSettingsRepository.loadKeyboardHeightPercent(context) + private var keyboardHeightPercent = ImeSettingsRepository.loadKeyboardHeightPercent( + context, + landscape = appliedOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE, + ) private var floatingWidthPercent = ImeSettingsRepository.loadFloatingWidthPercent(context) private var floatingOpacityPercent = ImeSettingsRepository.loadFloatingOpacityPercent(context) private var referenceScale = if (standalonePanel) 1f else ImeReferenceSizing.scale(context) @@ -748,6 +751,13 @@ open class ImeKeyboardView( currentExtraToggle = ::onState, currentFuzzy = { fuzzyEnabled }, currentKeyboardHeightPercent = { keyboardHeightPercent }, + currentKeyboardHeightLabel = { + if (appliedOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) { + "横屏键盘高度" + } else { + "竖屏键盘高度" + } + }, currentFloatingWidthPercent = { floatingWidthPercent }, currentFloatingOpacityPercent = { floatingOpacityPercent }, onThemeSelected = ::setTheme, @@ -994,6 +1004,10 @@ open class ImeKeyboardView( appliedOrientation = newConfig.orientation appliedFontScale = newConfig.fontScale appliedDensityDpi = newConfig.densityDpi + keyboardHeightPercent = ImeSettingsRepository.loadKeyboardHeightPercent( + context, + landscape = appliedOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE, + ) layoutMetrics = buildLayoutMetrics() if (!geometryChanged) return // The rows under an open panel are stale too (split layout, row heights): @@ -1790,7 +1804,11 @@ open class ImeKeyboardView( val bounded = percent.coerceIn(80, 120) if (keyboardHeightPercent == bounded) return keyboardHeightPercent = bounded - ImeSettingsRepository.saveKeyboardHeightPercent(context, bounded) + ImeSettingsRepository.saveKeyboardHeightPercent( + context, + bounded, + landscape = appliedOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE, + ) listener.onKeyboardHeightChanged(bounded) renderedMode = null layoutMetrics = buildLayoutMetrics() @@ -1819,7 +1837,10 @@ open class ImeKeyboardView( popup: Boolean, fuzzy: Boolean, ) { - val persistedHeight = ImeSettingsRepository.loadKeyboardHeightPercent(context) + val persistedHeight = ImeSettingsRepository.loadKeyboardHeightPercent( + context, + landscape = appliedOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE, + ) val persistedFloatingWidth = ImeSettingsRepository.loadFloatingWidthPercent(context) val persistedFloatingOpacity = ImeSettingsRepository.loadFloatingOpacityPercent(context) val floatingStyleChanged = diff --git a/app/src/main/java/llc/slacker/openime/keyboard/Pinyin9KeyboardRenderer.kt b/app/src/main/java/llc/slacker/openime/keyboard/Pinyin9KeyboardRenderer.kt index 1bf104c8..1fdddd42 100644 --- a/app/src/main/java/llc/slacker/openime/keyboard/Pinyin9KeyboardRenderer.kt +++ b/app/src/main/java/llc/slacker/openime/keyboard/Pinyin9KeyboardRenderer.kt @@ -83,20 +83,13 @@ internal class Pinyin9KeyboardRenderer( toPx(nineGridHeightDp()), ), ) - val centerBottom = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - } + val centerBottom = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL } val weights = ProductionKeyPolicy.nineKeyBottomRowWeights() centerBottom.addView( - createKey("123", true, null, ImeTypographyTokens.BODY_SP, onDigits).apply { - markSideKey(this) - }, + createKey("123", true, null, ImeTypographyTokens.BODY_SP, onDigits).apply { markSideKey(this) }, flexKeyParams(weights.side), ) - centerBottom.addView( - createSpaceVoiceKey("空格", onSpace), - flexKeyParams(weights.space), - ) + centerBottom.addView(createSpaceVoiceKey("空格", onSpace), flexKeyParams(weights.space)) centerBottom.addView( createKey("中/英", true, null, ImeTypographyTokens.BODY_SP, onModeSwitch).apply { tag = "key:mode" @@ -115,12 +108,8 @@ internal class Pinyin9KeyboardRenderer( orientation = LinearLayout.VERTICAL tag = "pinyin9-actions" } - // 删除 · 重输 · 0 · 确定, one key row each: 0 has its own key, as on a - // phone keypad, instead of only a swipe-up on 1. - side.addView( - createBackspaceKey().apply { markSideKey(this) }, - sideKeyParams(nineBodyHeightDp() / 4), - ) + // Keep the zero key separate from retype and enter, as on the phone keypad. + side.addView(createBackspaceKey().apply { markSideKey(this) }, sideKeyParams(nineBodyHeightDp() / 4)) side.addView( createKey("重输", true, null, ImeTypographyTokens.BODY_SP, onRetranslate).apply { tag = "key-retype" @@ -144,27 +133,25 @@ internal class Pinyin9KeyboardRenderer( sideKeyParams(nineBodyHeightDp() / 4), ) if (splitLayout()) { - // Landscape split (Sogou's 左右分离): a thumb's worth of nine-key on one side, - // a panel of common words on the other, and a mirror key between them. - val keys = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL } - keys.addView(side, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)) - keys.addView(center, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 25f / 7f)) - keys.addView(left, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)) - val gap = LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - gravity = android.view.Gravity.CENTER - addView( - createKey("镜像", true, null, ImeTypographyTokens.BODY_SP, onMirror).apply { - tag = "key-mirror" - contentDescription = "镜像,左右互换" - markSideKey(this) - }, - LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, toPx(keyRowHeightDp())), - ) + // Keep all Chinese keys in their familiar three-by-three order. + // The other hand gets direct numbers and the existing symbol rail. + val numbers = buildNumberPad() + val numberHand = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + tag = "pinyin9-number-hand" + addView(left, adaptiveColumnParams(1f)) + addView(numbers, adaptiveColumnParams(3f)) + } + val chineseHand = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + tag = "pinyin9-chinese-hand" + addView(center, adaptiveColumnParams(3f)) + addView(side, adaptiveColumnParams(1f)) } - val halves = if (mirrored()) listOf(wordsPanel() to 4.8f, gap to 1.2f, keys to 5.57f) - else listOf(keys to 5.57f, gap to 1.2f, wordsPanel() to 4.8f) - halves.forEach { (view, weight) -> container.addView(view, adaptiveColumnParams(weight)) } + val hands = if (mirrored()) listOf(chineseHand, numberHand) else listOf(numberHand, chineseHand) + container.addView(hands[0], adaptiveColumnParams(1f)) + container.addView(View(context), LinearLayout.LayoutParams(toPx(24), toPx(nineBodyHeightDp()))) + container.addView(hands[1], adaptiveColumnParams(1f)) } else { container.addView(left, adaptiveColumnParams(1f)) container.addView(center, adaptiveColumnParams(25f / 7f)) @@ -180,76 +167,93 @@ internal class Pinyin9KeyboardRenderer( ) } - /** Four rows of five frequent words; a tap types the word. */ - private fun wordsPanel(): LinearLayout = LinearLayout(context).apply { + private fun buildNineGrid(): LinearLayout { + val grid = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL } + NINE_KEYS.forEach { rowDefinition -> + val row = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL } + rowDefinition.forEach { (digit, _) -> row.addView(createNineDigitKey(digit), flexKeyParams()) } + grid.addView( + row, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + toPx(keyRowHeightDp()), + ), + ) + } + return grid + } + + private fun buildNumberPad(): LinearLayout = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL - tag = "split-words" - WORDS.forEach { rowWords -> + (1..9).chunked(3).forEach { digits -> addView( LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL - rowWords.forEach { word -> - addView( - createKey(word, false, null, ImeTypographyTokens.BODY_SP) { onCommitCharacter(word) }.apply { - tag = "key-word:$word" - markWhiteKey(this) - }, - LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f), - ) + digits.forEach { digit -> + addView(createKey(digit.toString(), false, null, ImeTypographyTokens.KEY_LETTER_SP) { + onCommitCharacter(digit.toString()) + }.apply { + tag = "key-split-number:$digit" + contentDescription = "数字 $digit" + markWhiteKey(this) + }, flexKeyParams()) } }, - LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f), + sideKeyParams(keyRowHeightDp()), ) } + addView( + LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + addView(createKey(",", true, null, ImeTypographyTokens.BODY_SP) { + onCommitCharacter(",") + }.apply { tag = "key-punctuation"; markSideKey(this) }, flexKeyParams()) + addView(createKey("0", false, null, ImeTypographyTokens.KEY_LETTER_SP) { + onCommitCharacter("0") + }.apply { tag = "key-split-number:0"; markWhiteKey(this) }, flexKeyParams()) + addView(createKey("互换", true, null, ImeTypographyTokens.BODY_SP, onMirror).apply { + tag = "key-mirror" + contentDescription = "左右互换" + markSideKey(this) + }, flexKeyParams()) + }, + sideKeyParams(keyRowHeightDp()), + ) } - private fun buildNineGrid(): LinearLayout { - val grid = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL } - NINE_KEYS.forEachIndexed { rowIndex, rowDefinition -> - val row = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL } - rowDefinition.forEach { (digit, subLabel) -> - val segmentation = digit == "1" - val key = createKey( - if (segmentation) "分词" else subLabel, - false, - digit, - if (segmentation) ImeTypographyTokens.BODY_SP else ImeTypographyTokens.CANDIDATE_SP, - ) { - if (segmentation) onPinyinSegment() else onNineKey(digit) - }.apply { - tag = "key-9:$digit" - contentDescription = if (segmentation) "1,分词" else digit - markWhiteKey(this) - if (segmentation) { - setOnLongClickListener { - onShowChoicePopup(this, listOf("@", "#", "/")) - true - } - } else { - val rows = NineKeyLongPressPolicy.choiceRows(digit) - if (rows.isNotEmpty()) { - setOnLongClickListener { - onShowChoiceRows(this, rows) - true - } - } + private fun createNineDigitKey(digit: String): ImeKeyView { + val segmentation = digit == "1" + val labels = NINE_KEYS.flatten().first { it.first == digit }.second + val key = createKey( + if (segmentation) "分词" else labels, + false, + digit, + if (segmentation) ImeTypographyTokens.BODY_SP else ImeTypographyTokens.CANDIDATE_SP, + ) { + if (segmentation) onPinyinSegment() else onNineKey(digit) + }.apply { + tag = "key-9:$digit" + contentDescription = if (segmentation) "1,分词" else digit + markWhiteKey(this) + if (segmentation) { + setOnLongClickListener { + onShowChoicePopup(this, listOf("@", "#", "/")) + true + } + } else { + val rows = NineKeyLongPressPolicy.choiceRows(digit) + if (rows.isNotEmpty()) { + setOnLongClickListener { + onShowChoiceRows(this, rows) + true } - // Swipe up types the digit without waiting for a long press. - onSwipeUp = { onCommitCharacter(digit) } - swipeUpEnabled = this@Pinyin9KeyboardRenderer.swipeUpEnabled } - onDigitKeyCreated(digit, key) - row.addView(key, flexKeyParams()) } - grid.addView( - row, - LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - toPx(keyRowHeightDp()), - ), - ) + onSwipeUp = { onCommitCharacter(digit) } + swipeUpEnabled = this@Pinyin9KeyboardRenderer.swipeUpEnabled } - return grid + onDigitKeyCreated(digit, key) + return key } private fun flexKeyParams(weight: Float = 1f) = @@ -273,12 +277,6 @@ internal class Pinyin9KeyboardRenderer( ) private companion object { - val WORDS = listOf( - listOf("的", "了", "我", "你", "是"), - listOf("吗", "好", "啊", "有", "吧"), - listOf("就", "在", "去", "没", "要"), - listOf("不", "都", "没有", "说", "这个"), - ) val NINE_KEYS = listOf( listOf("1" to "@#", "2" to "ABC", "3" to "DEF"), listOf("4" to "GHI", "5" to "JKL", "6" to "MNO"), diff --git a/app/src/main/java/llc/slacker/openime/panel/SettingsPanelController.kt b/app/src/main/java/llc/slacker/openime/panel/SettingsPanelController.kt index def5f37e..176f7370 100644 --- a/app/src/main/java/llc/slacker/openime/panel/SettingsPanelController.kt +++ b/app/src/main/java/llc/slacker/openime/panel/SettingsPanelController.kt @@ -67,6 +67,7 @@ internal class SettingsPanelController( private val currentExtraToggle: (String) -> Boolean, private val currentFuzzy: () -> Boolean, private val currentKeyboardHeightPercent: () -> Int, + private val currentKeyboardHeightLabel: () -> String, private val currentFloatingWidthPercent: () -> Int, private val currentFloatingOpacityPercent: () -> Int, private val onThemeSelected: (ImeTheme) -> Unit, @@ -175,7 +176,7 @@ internal class SettingsPanelController( content.addSection("外观", first = true) content.addCard( segmentedRow("外观", R.drawable.ic_pref_appearance), - sliderRow("键盘高度", R.drawable.ic_pref_height, 80, 120, currentKeyboardHeightPercent(), onChange = onKeyboardHeightChanged), + sliderRow(currentKeyboardHeightLabel(), R.drawable.ic_pref_height, 80, 120, currentKeyboardHeightPercent(), onChange = onKeyboardHeightChanged), ) content.addSection("横屏") content.addCard(landscapeLayoutRow(R.drawable.ic_pref_width)) @@ -233,7 +234,7 @@ internal class SettingsPanelController( ) content.addCard( segmentedRow("外观", iconRes = 0), - sliderRow("键盘高度", 0, 80, 120, currentKeyboardHeightPercent(), onChange = onKeyboardHeightChanged), + sliderRow(currentKeyboardHeightLabel(), 0, 80, 120, currentKeyboardHeightPercent(), onChange = onKeyboardHeightChanged), sliderRow("震动强度", 0, 10, 100, currentHapticStrengthPercent(), onChange = onHapticStrengthChanged), topMarginDp = ImeSpacingTokens.MD_DP, ) diff --git a/app/src/main/java/llc/slacker/openime/theme/ImeDesignTokens.kt b/app/src/main/java/llc/slacker/openime/theme/ImeDesignTokens.kt index fca766a6..b5ebb980 100644 --- a/app/src/main/java/llc/slacker/openime/theme/ImeDesignTokens.kt +++ b/app/src/main/java/llc/slacker/openime/theme/ImeDesignTokens.kt @@ -47,7 +47,7 @@ internal object ImeGeometryTokens { const val KEY_POPUP_MIN_WIDTH_DP = 40 const val KEY_POPUP_WIDTH_SCALE = 1.08f const val KEY_POPUP_VERTICAL_GAP_DP = 8 - const val LANDSCAPE_KEY_ROW_HEIGHT_DP = 40 + const val LANDSCAPE_KEY_ROW_HEIGHT_DP = 48 // The toolbar itself is compact; the top zone still reserves the larger // composed height so typing never moves the keyboard window. const val TOOLBAR_HEIGHT_DP = TOUCH_TARGET_DP @@ -57,13 +57,13 @@ internal object ImeGeometryTokens { const val STEP_MARK_SIZE_DP = 32 const val HERO_MARK_SIZE_DP = 72 const val COMPOSED_TOP_ZONE_HEIGHT_DP = 60 - // Landscape: one strip a little taller than a key row, pinyin and candidates side by side. + // Landscape: keep the same composing/candidate hierarchy, compact enough to leave room for the editor. // Space under the bottom row (the keys sit 7dp higher than they used to). const val KEYBOARD_TOP_PAD_DP = 4 const val KEYBOARD_BOTTOM_PAD_DP = 19 // Landscape keys are short, so the gap under them shrinks (HeliBoard and fcitx5 use none). const val LANDSCAPE_BOTTOM_PAD_DP = 6 - const val LANDSCAPE_TOP_ZONE_HEIGHT_DP = 48 + const val LANDSCAPE_TOP_ZONE_HEIGHT_DP = 54 } /** Shared spacing scale for every non-keyboard layout. */ diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index d1d8edb1..46b2c97c 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -62,7 +62,7 @@ Verification: `HardwareKeyPolicyTest`, `core_regression.sh` cases 040 to 043. openIME supports these environments: -- landscape (never uses fullscreen extract mode; the default is a floating keyboard, with full width and split halves as options) +- landscape (never uses fullscreen extract mode; the default keeps the docked keyboard full width, with floating and split layouts as options; keyboard height is adjustable independently from portrait and shared by all three landscape layouts) - font scale 130% and 200% (key labels grow to at most 1.3 times, and function key labels shrink to fit) - dark theme - small and narrow screens diff --git a/scripts/design_reference_e2e.py b/scripts/design_reference_e2e.py index 79317b85..132d0df5 100644 --- a/scripts/design_reference_e2e.py +++ b/scripts/design_reference_e2e.py @@ -198,7 +198,7 @@ def pref_items(items): command('clear-swipe'); command('mode:PINYIN_9') capture(stem + '-nine-idle', ('key-9:9',)) command('mode:ENGLISH_26'); capture(stem + '-english26', ('key:q',)) - for label,target,required in [('工具','tools',('tool:设置',)),('切换键盘','keyboard-select',('keyboard-choice-selected',)),('符号','symbols',('symbol-categories',)),('表情','emoji',('emoji-cell',)),('文本编辑','text-editor',('textedit-cross',)),('剪贴板','clipboard',('clip-card',)),('语音输入','voice',('voice-mic',)),('设置','preferences',('settings-slider:键盘高度',))]: + for label,target,required in [('工具','tools',('tool:设置',)),('切换键盘','keyboard-select',('keyboard-choice-selected',)),('符号','symbols',('symbol-categories',)),('表情','emoji',('emoji-cell',)),('文本编辑','text-editor',('textedit-cross',)),('剪贴板','clipboard',('clip-card',)),('语音输入','voice',('voice-mic',)),('设置','preferences',('settings-slider:竖屏键盘高度',))]: if label == '工具': command('mode:PINYIN_9'); tap('更多'); capture(stem + '-' + target, required) else: panel(label,stem + '-' + target,required) @@ -234,7 +234,7 @@ def pref_items(items): ('表情','emoji',('emoji-cell',)), ('文本编辑','text-editor',('textedit-cross', 'textedit-action:paste')), ('剪贴板','clipboard',('segmented-track',)), - ('设置','preferences',('segmented-track-tall', 'settings-slider:键盘高度')), + ('设置','preferences',('segmented-track-tall', 'settings-slider:竖屏键盘高度')), ]: panel(label, stem + '-' + target, required) launch()