Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ GitHub Release 的发布说明;版本与发布流程见 [docs/RELEASE.md](docs

## [Unreleased]

## [0.0.7-beta.1] - 2026-10-06

第七个测试版:九键预编辑不再出现数字,拼音栏点哪个都管用,符号、表情、颜文字大幅补全。从 0.0.6-beta.1 覆盖安装即可,用户数据保留。

- 修复九键预编辑行有时显示数字(如“669”或一整串数字):没有词能读完全部数字、或首个候选只覆盖前半段时,预编辑退回了原始数字。现在任何时候都显示拼音字母:先按首个候选的读音对齐(含简拼,如 669 打出“模型”显示 mo'x),剩下的按完整音节和未打完音节的开头拼出。
- 修复九键左侧拼音栏点只有声母的选项(如单按 6 后的 m、n)没反应:现在点了就按这个字母筛选候选(m → 没、么、们)。
- 九键左侧符号栏改成一整块可滑动的列表:符号之间用细线分隔,上下边缘渐隐,提示可以上下滑;最下面的“+”加了圆圈。数字键盘的符号栏同样调整。
- 九键右侧一列加“0”键:删除、重输、0、确定。
- 表情补全为 Unicode 18.0 的全部基础表情(约 1900 个,原有顺序不变,新增的排在各分类后面);手机字体显示不了的新表情自动隐藏,不会出现方框。
- 符号大幅扩充:中文标点、特殊符号(星、心、勾叉、音乐、天气、星座等)、颜文字增加到 100 多个,新增“拼音”(带声调字母)、“日文”(平假名、片假名)、“注音”、“制表”(制表线和方块)分类。
- 修复颜文字在符号面板里显示不全:较长的符号改用每行 3 个的宽格子。
- 切换键盘的五个图标统一成同一种样式(与工具栏的键盘图标同一个外框),工具页和切换键盘页的图标大小一致。
- 新的应用图标:白底上的“IME”,I 是蓝色的文字光标,ME 为深色。桌面图标、App 首页标志、启动画面和 README 图片用同一个标志;Android 13 以上支持主题图标(单色层)。
- 开关按 iOS 的比例重做(51×31,圆钮 27):圆钮离四边都是 2dp,开和关两种状态完全对称,带一层淡阴影,按住时朝移动方向轻轻拉长。之前关闭时圆钮贴着左边、打开时离右边 4dp。整个应用的开关都是这一个组件。
- 修复模糊音页里关着的子开关仍显示蓝色底。
- 偏好设置和首页对齐重做:每行的图标都在整行里上下居中;“音效”“震动手感”“震动强度”是上一行开关的子设置,不再重复放同一个图标,改为缩进到父开关的文字下面;开关、箭头、按钮的右边缘落在同一条线上;滑块轨道两端对齐标题和百分比;每行上下留白一致。首页的步骤圆圈、语音图标和“已就绪”标记统一为 32dp,几张卡片的文字列对齐;标题区不再比卡片多缩进 4dp。模糊音页等没有图标的行,分隔线改为从文字起点开始(之前比文字多缩进 44dp)。新增偏好页和模糊音页的几何测试,锁住这些规则。
- 构建工具升级(Gradle 8.14、Android Gradle 插件 8.13、Kotlin 2.4)。

## [0.0.6-beta.1] - 2026-10-05

第六个测试版:Rime 词库在打包时预先编译好,安装或升级后不用再等一分钟。从 0.0.5-beta.1 覆盖安装即可,用户数据保留。
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.6-beta.1
0.0.7-beta.1
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ dependencies {
implementation(files("libs/sherpa-onnx-1.13.6.aar"))
testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20260814")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:1.7.0")
}

// Kotlin 2.1 writes JVM unit-test classes to tmp/kotlin-classes while this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class InteractionPolishInstrumentedTest {

@Test
fun emojiRecentIsMostRecentlyUsedAndDeduplicated() {
// Start from no recents: earlier sessions on the device (or E2E runs) leave some.
EmojiRecentRepository.clear(context)
EmojiRecentRepository.record(context, "😀")
EmojiRecentRepository.record(context, "😂")
EmojiRecentRepository.record(context, "😀")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package llc.slacker.openime

import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import androidx.test.ext.junit.runners.AndroidJUnit4
import llc.slacker.openime.theme.ImeGeometryTokens
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.math.abs

/**
* The preferences page keeps one grid: icon tiles centred on their row, every
* label on one text column, and every trailing control ending on one line.
*/
@RunWith(AndroidJUnit4::class)
class PreferencesGeometryInstrumentedTest {

@Test
fun rowsShareOneTextColumnAndOneTrailingEdge() {
DirectActivityHarness(ImeSettingsActivity::class.java).use { harness ->
harness.launch()
harness.awaitMain { activity ->
val root = activity.window.decorView
val rows = collect(root) { it.tag == "setting-row" }.filterIsInstance<ViewGroup>()
if (rows.size < 10 || rows.any { it.width == 0 }) return@awaitMain null
val density = activity.resources.displayMetrics.density
val slack = 1

val labelLefts = rows.flatMap { row -> collect(row) { it.tag == "setting-label" } }.map { left(it) }
assertTrue("Every label starts on one text column: $labelLefts", labelLefts.max() - labelLefts.min() <= slack)
val textColumn = labelLefts.min()

rows.forEach { row ->
val trailingEdge = left(row) + row.width - row.paddingRight
collect(row) { it.tag == "setting-icon" }.forEach { icon ->
val iconCentre = top(icon) + icon.height / 2
val rowCentre = top(row) + row.height / 2
assertTrue("Icon is centred on its row (${row.contentDescription})", abs(iconCentre - rowCentre) <= slack)
}
collect(row) { it.tag == "toggle" }.forEach { toggle ->
assertTrue("Switch ends on the row's 16dp line", abs(left(toggle) + toggle.width - trailingEdge) <= slack)
}
collect(row) { it.tag == "setting-chevron" }.forEach { chevron ->
val glyphEnd = left(chevron) + chevron.width - (ImeGeometryTokens.CHEVRON_GLYPH_END_INSET_DP * density).toInt()
assertTrue("Chevron glyph ends on the row's 16dp line", abs(glyphEnd - trailingEdge) <= slack + 1)
}
collect(row) { it is SeekBar }.forEach { bar ->
val trackStart = left(bar) + bar.paddingLeft
val trackEnd = left(bar) + bar.width - bar.paddingRight
assertTrue("Slider track starts on the text column", abs(trackStart - textColumn) <= slack)
assertTrue("Slider track ends on the row's 16dp line", abs(trackEnd - trailingEdge) <= slack)
}
}
assertHairlinesStartOnText(root)
true
}
}
}

@Test
fun fuzzyPageHairlinesStartOnTheRuleText() {
DirectActivityHarness(ImeSettingsActivity::class.java).use { harness ->
harness.launch()
harness.awaitMain { activity ->
val entry = collect(activity.window.decorView) { it.tag == "setting-row" && it.contentDescription?.startsWith("模糊音与智能纠错") == true }
.firstOrNull() ?: return@awaitMain null
entry.performClick()
true
}
harness.awaitMain { activity ->
val root = activity.window.decorView
val hairlines = collect(root) { it.tag == "row-hairline" }
if (hairlines.size < 8 || hairlines.any { it.width == 0 }) return@awaitMain null
assertHairlinesStartOnText(root)
true
}
}
}

/** Each hairline starts where the text of the row above it starts. */
private fun assertHairlinesStartOnText(root: View) {
collect(root) { it.tag == "row-hairline" }.forEach { line ->
val card = line.parent as ViewGroup
val above = card.getChildAt(card.indexOfChild(line) - 1)
val label = collect(above) { it.tag == "setting-label" }.firstOrNull() ?: return@forEach
assertTrue(
"Hairline under ${(above as? ViewGroup)?.contentDescription} starts on its text",
abs(left(line) - left(label)) <= 1,
)
}
}

private fun left(view: View): Int = IntArray(2).also { view.getLocationInWindow(it) }[0]

private fun top(view: View): Int = IntArray(2).also { view.getLocationInWindow(it) }[1]

private fun collect(root: View, match: (View) -> Boolean): List<View> {
val found = mutableListOf<View>()
fun walk(view: View) {
if (view.visibility != View.VISIBLE) return
if (match(view)) found += view
if (view is ViewGroup) for (index in 0 until view.childCount) walk(view.getChildAt(index))
}
walk(root)
return found
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class DebugKeyboardActivity : Activity(), ImeKeyboardView.Listener, CandidateRes
override fun nineKeyReadingFor(digits: String, candidate: String): List<String>? =
candidatePipeline.nineKeyReadingFor(digits, candidate)

override fun nineKeyPreviewFor(digits: String, candidate: String): List<String>? =
candidatePipeline.nineKeyPreviewFor(digits, candidate)

override fun nineKeyPathsFor(code: String?): List<String> =
candidatePipeline.nineKeyPathsFor(code)

Expand Down
Loading
Loading