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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ GitHub Release 的发布说明;版本与发布流程见 [docs/RELEASE.md](docs

## [Unreleased]

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

第五个测试版:按键震动更响更清脆并可调强度,按键音效恢复,“中/英”键移到空格右边,整理“关于与数据”和工具面板。从 0.0.4-beta.1 覆盖安装即可,用户数据保留。

- 按键震动更响更清脆:不再走系统给键盘的固定低振幅,直接驱动振动马达播放满强度的短促点击(Android 11 起用 CLICK 原语,Android 10 用系统预设点击,更早版本用 20ms 满幅脉冲),仍跟随系统“触摸反馈”强度;删除键手势到位也是同一下点击。
- 新增“震动强度”滑块(10%–100%,默认 100%),在偏好设置“触感震动”下方和键盘内设置首屏;拖动时实时试震,随用户数据导出、导入。
- 修复“按键音效”打开后没有声音:系统“触摸提示音”关闭时按键音也被一起静音,现在只看 App 内的开关。
- 26 键的“中/英”键移到空格右边,与“,。”键对调,中英文键盘一致。
- 删除键上滑清空后不再出现“5 秒内下滑撤回”和顶栏的“已清空 · 撤销”;工具栏的普通撤销按钮保留。
- “关于与数据”拆成两页:“关于”(版本、隐私、诊断)和“数据管理”(导出与导入用户数据、语音词表、卸载前提示)。偏好设置和键盘内设置的最后一组改名为“关于与数据”,下面是这两项。
- 键盘工具面板去掉“语音输入”(按住空格即可)和“切换键盘”(工具栏已有),新增“数据管理”。

## [0.0.4-beta.1] - 2026-10-04

第四个测试版:松开空格立即结束录音,已录音频继续完整转写;加入离线中英标点恢复,重新整理 App 首页与设置页。
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ openIME 是一款独立的 Android 系统输入法。拼音候选、用户词库
- **九键**:输入时左栏列出下一个字的拼音,一个字选一个音节,选定后自动移到下一个字。
- **语音输入**:长按空格说话,松手后识别并上屏;使用内置的中英双语模型,不联网;可去掉“嗯”“呃”等语气词,也可把标点写成空格。
- **语音词表**:内置科技、应用、游戏词表,可导入自己的文本词表;识别后把同音词改成词表写法,不联网。
- **手势**:删除键上滑清空,5 秒内下滑可撤回;空格左右滑动移动光标,滑动时底行其他按键锁定。
- **手势**:删除键上滑清空;空格左右滑动移动光标,滑动时底行其他按键锁定。
- **表情联想**:选词后联想栏先给出相关表情(开心 → 😊),词表内置,不联网。
- **自动填充**:Android 11+ 上,密码管理器的账号、验证码直接显示在键盘工具栏位置。
- **数字和符号**:26 键字母键右上角印着数字和符号,上滑或长按直接输入。
- **适配**:横竖屏、平板与折叠屏、深色模式、大字号;终端、远程桌面、游戏等原始按键输入框;
外接键盘可直接打拼音。详见 [兼容性说明](docs/COMPATIBILITY.md)。
- **自我保护**:连续崩溃或卡死后自动进入安全模式,保证仍然可以打字;
“设置 → 关于与数据”可复制诊断信息并导出、导入用户数据。
“设置 → 关于与数据”下,“关于”可复制诊断信息,“数据管理”可导出、导入用户数据。

## 下载与安装

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.4-beta.1
0.0.5-beta.1
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class AuditInteractionInstrumentedTest {
}
"onFuzzyChanged" -> { fuzzyChanges.add(args!![0] as Boolean); null }
// A proxy answers null for everything else; a primitive boolean
// query (hasClearUndo, onUndoClear) must answer false instead.
// query must answer false instead.
else -> if (method.returnType == java.lang.Boolean.TYPE) false else null
}
} as ImeKeyboardView.Listener
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Key-press clicks drive the vibrator directly (normal permission, granted at install). -->
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:allowBackup="false"
Expand Down Expand Up @@ -49,9 +51,14 @@
android:label="@string/activity_label_hotwords" />

<activity
android:name=".AboutDataActivity"
android:name=".AboutActivity"
android:exported="false"
android:label="@string/activity_label_about_data" />
android:label="@string/activity_label_about" />

<activity
android:name=".DataManagementActivity"
android:exported="false"
android:label="@string/activity_label_data" />

<service
android:name=".LocalVoiceImeService"
Expand Down
148 changes: 148 additions & 0 deletions app/src/main/java/llc/slacker/openime/AboutActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package llc.slacker.openime

import android.app.Activity
import android.os.Bundle
import android.view.WindowInsets
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import llc.slacker.openime.core.CrashGuard
import llc.slacker.openime.setup.SetupUi
import llc.slacker.openime.theme.ImeGeometryTokens
import llc.slacker.openime.theme.ImeSpacingTokens
import llc.slacker.openime.theme.ImeTypographyTokens

/** 关于: privacy, diagnostics and the version. User data lives in [DataManagementActivity]. */
class AboutActivity : Activity() {
override fun attachBaseContext(newBase: android.content.Context) {
super.attachBaseContext(SetupUi.appearanceContext(newBase))
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CrashGuard.install(this)

val content = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(
dp(ImeSpacingTokens.LG_DP),
0,
dp(ImeSpacingTokens.LG_DP),
dp(ImeSpacingTokens.XXL_DP),
)
addView(
SetupUi.activityTopBar(
context = this@AboutActivity,
title = "关于",
onBack = ::finish,
),
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dp(ImeGeometryTokens.TOP_BAR_HEIGHT_DP),
).apply { marginStart = -dp(16); marginEnd = -dp(16) },
)
addView(
SetupUi.infoCard(
this@AboutActivity,
title = "隐私",
body = "本应用不含联网权限,数据只存在本机。",
iconRes = R.drawable.ic_pref_shield,
),
wrap().apply { topMargin = dp(ImeSpacingTokens.SM_DP) },
)
addView(diagnosticsCard(), wrap().apply { topMargin = dp(ImeSpacingTokens.LG_DP) })
addView(TextView(this@AboutActivity).apply {
text = "openIME · 版本 " + versionName()
textSize = ImeTypographyTokens.SMALL_SP
gravity = android.view.Gravity.CENTER
setTextColor(getColor(R.color.setup_body))
}, wrap().apply { topMargin = dp(18) })
}

setContentView(
ScrollView(this).apply {
setBackgroundColor(getColor(R.color.setup_page_bg))
isFillViewport = true
setOnApplyWindowInsetsListener { view, insets ->
if (android.os.Build.VERSION.SDK_INT >= 30) {
val bars = insets.getInsets(
WindowInsets.Type.systemBars() or
WindowInsets.Type.displayCutout(),
)
view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
} else {
@Suppress("DEPRECATION")
view.setPadding(
insets.systemWindowInsetLeft,
insets.systemWindowInsetTop,
insets.systemWindowInsetRight,
insets.systemWindowInsetBottom,
)
}
insets
}
addView(content)
},
)
}

private fun diagnosticsCard(): LinearLayout {
val safeMode = CrashGuard.isSafeMode(this)
val lastReport = CrashGuard.lastReport(this)
val card = SetupUi.infoCard(
this,
title = "诊断",
body = when {
safeMode -> "输入法刚才多次异常退出,已临时关闭原生词库和语音预加载;约 10 分钟后自动恢复,也可以现在退出。"
lastReport != null -> "最近一次异常:" + lastReport.lineSequence().first().substringAfter("| ").substringBefore(" | thread")
else -> "没有异常记录。"
} + "\n诊断信息只含异常类型和代码位置,不含任何输入内容;只有你点“复制”才会离开这里。",
iconRes = R.drawable.ic_pref_info,
)
if (lastReport == null && !safeMode) return card
val row = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
if (lastReport != null) {
addView(
SetupUi.secondaryButton(this@AboutActivity, "复制诊断信息") {
val clipboard = getSystemService(android.content.ClipboardManager::class.java)
clipboard.setPrimaryClip(android.content.ClipData.newPlainText("openIME diagnostics", lastReport))
Toast.makeText(this@AboutActivity, "已复制", Toast.LENGTH_SHORT).show()
},
LinearLayout.LayoutParams(0, dp(44), 1f).apply { marginEnd = dp(ImeSpacingTokens.SM_DP) },
)
}
if (safeMode) {
addView(
SetupUi.primaryButton(this@AboutActivity, "退出安全模式") {
CrashGuard.clearHistory(this@AboutActivity)
Toast.makeText(this@AboutActivity, "下次打开键盘时恢复完整功能", Toast.LENGTH_SHORT).show()
recreate()
},
LinearLayout.LayoutParams(0, dp(44), 1f),
)
}
}
card.addView(
row,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dp(44)).apply {
topMargin = dp(12)
marginStart = dp(44)
},
)
return card
}

@Suppress("DEPRECATION")
private fun versionName(): String =
packageManager.getPackageInfo(packageName, 0).versionName.orEmpty()
.ifBlank { "未知版本" }

private fun wrap() = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
)

private fun dp(value: Int): Int = SetupUi.dp(this, value)
}
Loading
Loading