docs: 新增 Android 大字号排版方案文档

新增 Android 大字号排版的设计与实施计划文档,便于跨设备继续开发并保持上下文一致。
This commit is contained in:
iammm0
2026-03-09 09:08:38 +08:00
parent 9cdd2bdf2f
commit cca8ce7731
2 changed files with 632 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
# app-android 大字模式排版与触达重构设计
日期2026-03-06
范围:`app-android`(仅客户端 UI 层)
状态:已完成设计评审(分节确认通过)
## 1. 背景与问题
当前大字模式主要放大字号,但没有同步优化字体家族、字重、行高、字间距和点击区域,导致:
- 聊天气泡、章节正文、按钮文案在大字模式下出现拥挤、跳行不自然
- 高频页面(聊天、阅读、设置)视觉节奏不一致
- 可读性和可触达性没有形成统一设计系统
## 2. 目标与非目标
### 2.1 目标
- 大字模式达到“稳、清晰、易读”
- 在聊天页、阅读页、设置页实现一致排版体验
- 以设计令牌token统一字体与触达尺寸并推广到全 app方案 C
- 与现有业务逻辑完全解耦,不改 API、不改数据模型
### 2.2 非目标
- 不改后端接口与协议
- 不做业务流程重构
- 不引入外部字体文件,字体家族采用系统无衬线
## 3. 方案决策
用户确认采用 **方案 C全局重构**
- 不仅修复三类高频页面,还将文本与触达规则推广到全 app UI
- 通过主题注入统一 token逐步清理硬编码 `sp`/固定小点击区
## 4. 设计架构
```mermaid
flowchart LR
settingsSwitch["ProfileSettingSwitch"] --> appSettingsState["AppSettings.largeFontModeState"]
appSettingsState --> lifeechoTheme["LifeechoTheme"]
lifeechoTheme --> typographyTokens["LocalTypographyTokens"]
lifeechoTheme --> touchTokens["LocalTouchTargetTokens"]
typographyTokens --> uiComponents["AllUIComponents"]
touchTokens --> uiComponents
```
### 4.1 Typography 令牌层
维护两套语义化排版令牌:
- `TypographyNormal`
- `TypographyLarge`
每个文本令牌至少包含:
- `fontFamily`:系统无衬线
- `fontSize`
- `lineHeight`(大字模式行高比保持 1.45~1.65
- `fontWeight`(正文不低于 500
- `letterSpacing`(微调,避免拥挤)
语义分组建议:
- `heading` / `title` / `body` / `caption`
- `button` / `bubble` / `reading` / `tab`
### 4.2 Touch Target 令牌层
维护两套触达尺寸令牌:
- `TouchTargetNormal`
- `TouchTargetLarge`
覆盖最小可触达规则:
- 按钮高度
- 列表项最小高度
- IconButton 点击区
- 输入框与语音按钮最小高度
大字模式建议最小触达范围52~56dp。
### 4.3 主题统一注入
`LifeechoTheme` 内根据 `AppSettings.largeFontModeState` 统一注入:
- `LocalTypographyTokens`
- `LocalTouchTargetTokens`
组件只读 token不直接写死 `sp/dp`
## 5. 迁移范围与顺序(全局)
### 5.1 第一层:主题与公共组件
- `ui/theme/Dimensions.kt`
- `ui/theme/Theme.kt`
- `ui/components/common/*``SectionCard``SettingItem``AppScaffold``MarkdownText`
### 5.2 第二层:高频页面相关组件
- 聊天:`ui/components/chat/*`
- 阅读:`ui/components/memoir/*`
- 设置:`ui/screens/ProfileScreen.kt`
### 5.3 第三层:全屏幕收敛
- `ui/screens/*` 全量扫描
- 清理硬编码 `sp`、过小点击区域、局部样式漂移
- 统一到语义 token
## 6. 关键规范
### 6.1 文本规范
- 正文字重下限500
- 标题/按钮可使用 600
- 大字模式行高比1.45~1.65
- 列表标题保留 `maxLines + ellipsis`
- 正文优先自然换行,避免过度截断
### 6.2 触达规范
- 所有主要交互控件使用 touch token 约束最小尺寸
- 对聊天输入区、语音按钮、设置项行高进行一致化处理
### 6.3 Markdown 一致性
- `MarkdownText` 由 typography token 推导正文/段落/引用/代码样式
- 保证 markdown 正文与普通 `Text` 的阅读节奏一致
## 7. 风险与回退
### 7.1 风险
- 全局改造范围大,易出现局部回归
- 历史硬编码样式分散,迁移遗漏风险高
### 7.2 缓解
- 按“公共组件 -> 高频页面 -> 全量页面”分批推进
- 每批保持可独立回退
- 业务逻辑与样式改造分离,降低故障影响面
## 8. 验收与测试DoD 对齐)
### 8.1 页面回归(普通/大字双模式)
- 聊天页:消息气泡、输入框、语音按钮、时间分隔
- 阅读页:目录、章节标题、正文、引用、浮动按钮
- 设置页:分组标题、设置项、开关、关键按钮
### 8.2 交互验收
- 任务3 分钟内完成“阅读章节 + 发送消息”
- 目标5 位测试用户无阅读障碍反馈
### 8.3 通过标准
- 大字模式下无明显截断/重叠/异常跳行
- 三类高频页面视觉节奏一致
- 关键交互均满足最小可触达尺寸规则
## 9. 后续
本设计已确认,可进入实现计划阶段(拆解为可执行任务与验证步骤)。

View File

@@ -0,0 +1,464 @@
# Android Large Typography Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:**`app-android` 中完成全局大字模式排版与触达重构,让大字模式下文本“稳、清晰、易读”,并保证聊天/阅读/设置等高频页面一致体验。
**Architecture:** 基于 `LifeechoTheme` 的双层 tokenTypography + TouchTarget注入将 UI 从硬编码 `sp/dp` 迁移到语义化 token。先改主题与公共组件再改聊天/阅读/设置高频模块,最后做全局扫尾与验收。
**Tech Stack:** Kotlin, Jetpack Compose Material3, Compose UI Test, JUnit4, Gradle
**Related Skills:** @superpowers:executing-plans, @superpowers:verification-before-completion
---
### Task 1: 建立双层令牌与主题切换入口
**Files:**
- Create: `app-android/app/src/main/java/com/huaga/life_echo/ui/theme/TypographyTokens.kt`
- Create: `app-android/app/src/main/java/com/huaga/life_echo/ui/theme/TouchTargetTokens.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/theme/Theme.kt`
- Test: `app-android/app/src/test/java/com/huaga/life_echo/ui/theme/TypographyTokensTest.kt`
- Test: `app-android/app/src/test/java/com/huaga/life_echo/ui/theme/TouchTargetTokensTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun largeTypography_bodyWeightAndLineHeightRatio_areAccessible() {
val large = largeTypographyTokens()
val body = large.bodyPrimary
val ratio = body.lineHeight.value / body.fontSize.value
assertTrue(body.fontWeight.weight >= FontWeight.Medium.weight)
assertTrue(ratio in 1.45f..1.65f)
}
```
```kotlin
@Test
fun largeTouchTarget_minimumHeights_areAtLeast52dp() {
val large = largeTouchTargetTokens()
assertTrue(large.buttonMinHeight.value >= 52f)
assertTrue(large.listItemMinHeight.value >= 52f)
assertTrue(large.iconTapMinSize.value >= 52f)
}
```
**Step 2: Run test to verify it fails**
Run: `cd app-android && ./gradlew :app:testDebugUnitTest --tests "com.huaga.life_echo.ui.theme.*TokensTest" -i`
Expected: FAILtoken API 尚不存在)
**Step 3: Write minimal implementation**
```kotlin
data class TextToken(
val fontSize: TextUnit,
val lineHeight: TextUnit,
val fontWeight: FontWeight,
val letterSpacing: TextUnit,
val fontFamily: FontFamily = FontFamily.SansSerif,
)
data class TypographyTokens(
val headingPrimary: TextToken,
val bodyPrimary: TextToken,
val bodySecondary: TextToken,
val caption: TextToken,
val button: TextToken,
val bubble: TextToken,
val readingTitle: TextToken,
val readingBody: TextToken,
)
```
```kotlin
data class TouchTargetTokens(
val buttonMinHeight: Dp,
val listItemMinHeight: Dp,
val iconTapMinSize: Dp,
val inputMinHeight: Dp,
)
```
```kotlin
@Composable
fun rememberTypographyTokens(): TypographyTokens {
val large = AppSettings.rememberLargeFontMode()
return remember(large) { if (large) largeTypographyTokens() else normalTypographyTokens() }
}
```
**Step 4: Run test to verify it passes**
Run: `cd app-android && ./gradlew :app:testDebugUnitTest --tests "com.huaga.life_echo.ui.theme.*TokensTest" -i`
Expected: PASS
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/theme/Theme.kt \
app/src/main/java/com/huaga/life_echo/ui/theme/TypographyTokens.kt \
app/src/main/java/com/huaga/life_echo/ui/theme/TouchTargetTokens.kt \
app/src/test/java/com/huaga/life_echo/ui/theme/TypographyTokensTest.kt \
app/src/test/java/com/huaga/life_echo/ui/theme/TouchTargetTokensTest.kt
git commit -m "feat(android): add typography and touch target token system"
```
### Task 2: 提供统一文本/触达 helper阻断页面硬编码继续扩散
**Files:**
- Create: `app-android/app/src/main/java/com/huaga/life_echo/ui/theme/TextTokenExtensions.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/theme/Dimensions.kt`
- Test: `app-android/app/src/test/java/com/huaga/life_echo/ui/theme/TextTokenExtensionsTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun toTextStyle_mapsTokenFieldsExactly() {
val token = TextToken(
fontSize = 18.sp,
lineHeight = 28.sp,
fontWeight = FontWeight.Medium,
letterSpacing = 0.2.sp,
fontFamily = FontFamily.SansSerif
)
val style = token.toTextStyle()
assertEquals(18.sp, style.fontSize)
assertEquals(28.sp, style.lineHeight)
assertEquals(FontWeight.Medium, style.fontWeight)
assertEquals(0.2.sp, style.letterSpacing)
}
```
**Step 2: Run test to verify it fails**
Run: `cd app-android && ./gradlew :app:testDebugUnitTest --tests "com.huaga.life_echo.ui.theme.TextTokenExtensionsTest" -i`
Expected: FAIL`toTextStyle()` 不存在)
**Step 3: Write minimal implementation**
```kotlin
fun TextToken.toTextStyle(): TextStyle = TextStyle(
fontSize = fontSize,
lineHeight = lineHeight,
fontWeight = fontWeight,
letterSpacing = letterSpacing,
fontFamily = fontFamily
)
```
并在 `Dimensions.kt` 中把原有 `AppTypographyData` 标记为过渡层,内部改为委托新 token避免双源
**Step 4: Run test to verify it passes**
Run: `cd app-android && ./gradlew :app:testDebugUnitTest --tests "com.huaga.life_echo.ui.theme.TextTokenExtensionsTest" -i`
Expected: PASS
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/theme/TextTokenExtensions.kt \
app/src/main/java/com/huaga/life_echo/ui/theme/Dimensions.kt \
app/src/test/java/com/huaga/life_echo/ui/theme/TextTokenExtensionsTest.kt
git commit -m "refactor(android): add text token style helpers"
```
### Task 3: 改造公共组件(设置页基石)
**Files:**
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/SettingItem.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/SectionCard.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/AppScaffold.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/MarkdownText.kt`
- Test: `app-android/app/src/androidTest/java/com/huaga/life_echo/ui/components/common/SettingItemLargeModeTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun settingItem_largeMode_hasMinTouchHeightAndReadableTypography() {
composeRule.setContent {
AppSettings.largeFontMode = true
LifeechoTheme {
SettingItem(icon = AppIcons.FormatSize, label = "大字模式", type = SettingItemType.TOGGLE, value = true)
}
}
composeRule.onNodeWithText("大字模式").assertExists()
composeRule.onNodeWithText("大字模式").assertIsDisplayed()
}
```
**Step 2: Run test to verify it fails**
Run:
`cd app-android && ./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.huaga.life_echo.ui.components.common.SettingItemLargeModeTest`
Expected: FAIL触达最小高度/排版尚未统一)
**Step 3: Write minimal implementation**
```kotlin
val typography = LocalTypographyTokens.current
val touch = LocalTouchTargetTokens.current
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = touch.listItemMinHeight)
.clickable(...)
)
```
`SectionCard`/`AppScaffold` 的标题与间距统一改为 token`MarkdownText` 的 paragraph/quote/code 同步走 token阅读页会复用
**Step 4: Run test to verify it passes**
Run: same command as Step 2
Expected: PASS
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/components/common/SettingItem.kt \
app/src/main/java/com/huaga/life_echo/ui/components/common/SectionCard.kt \
app/src/main/java/com/huaga/life_echo/ui/components/common/AppScaffold.kt \
app/src/main/java/com/huaga/life_echo/ui/components/common/MarkdownText.kt \
app/src/androidTest/java/com/huaga/life_echo/ui/components/common/SettingItemLargeModeTest.kt
git commit -m "refactor(android): unify common component typography and touch targets"
```
### Task 4: 改造聊天组件(气泡 + 输入 + 语音)
**Files:**
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/ChatInputField.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/VoiceRecordButton.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt`
- Test: `app-android/app/src/androidTest/java/com/huaga/life_echo/ui/components/chat/LargeModeChatTypographyTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun chatInput_largeMode_respectsMinHeightAndReadableText() {
composeRule.setContent {
AppSettings.largeFontMode = true
LifeechoTheme {
ChatInputField(value = "", onValueChange = {}, onSend = {})
}
}
composeRule.onNodeWithText("输入消息...").assertExists()
}
```
**Step 2: Run test to verify it fails**
Run:
`cd app-android && ./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.huaga.life_echo.ui.components.chat.LargeModeChatTypographyTest`
Expected: FAIL输入框/语音按钮仍有硬编码高度与字体)
**Step 3: Write minimal implementation**
```kotlin
val typography = LocalTypographyTokens.current
val touch = LocalTouchTargetTokens.current
OutlinedTextField(
textStyle = typography.bodyPrimary.toTextStyle(),
modifier = Modifier.heightIn(min = touch.inputMinHeight)
)
```
并将聊天气泡正文、流式文本、时间分隔、语音按钮文案全部迁移到 token。
**Step 4: Run test to verify it passes**
Run: same command as Step 2
Expected: PASS
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt \
app/src/main/java/com/huaga/life_echo/ui/components/chat/ChatInputField.kt \
app/src/main/java/com/huaga/life_echo/ui/components/chat/VoiceRecordButton.kt \
app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt \
app/src/androidTest/java/com/huaga/life_echo/ui/components/chat/LargeModeChatTypographyTest.kt
git commit -m "refactor(android): migrate chat typography and touch areas to tokens"
```
### Task 5: 改造阅读组件(章节正文/标题/目录)
**Files:**
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterReadingView.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/FullTextReadingView.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt`
- Test: `app-android/app/src/androidTest/java/com/huaga/life_echo/ui/components/memoir/LargeModeReadingTypographyTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun readingView_largeMode_usesReadableHeadingAndBody() {
composeRule.setContent {
AppSettings.largeFontMode = true
LifeechoTheme {
ChapterReadingView(chapter = sampleChapter())
}
}
composeRule.onNodeWithText("第1章").assertExists()
composeRule.onNodeWithText(sampleChapter().title).assertExists()
}
```
**Step 2: Run test to verify it fails**
Run:
`cd app-android && ./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.huaga.life_echo.ui.components.memoir.LargeModeReadingTypographyTest`
Expected: FAIL阅读页仍存在硬编码 `sp` 与固定行高)
**Step 3: Write minimal implementation**
```kotlin
Text(
text = chapter.title,
style = LocalTypographyTokens.current.readingTitle.toTextStyle()
)
```
```kotlin
MarkdownText(
content = processedContent,
fontSize = LocalTypographyTokens.current.readingBody.fontSize.value.toInt(),
lineHeight = LocalTypographyTokens.current.readingBody.lineHeight.value.toInt()
)
```
并将目录卡片/章号/提示文案统一迁移到 token。
**Step 4: Run test to verify it passes**
Run: same command as Step 2
Expected: PASS
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterReadingView.kt \
app/src/main/java/com/huaga/life_echo/ui/components/memoir/FullTextReadingView.kt \
app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt \
app/src/androidTest/java/com/huaga/life_echo/ui/components/memoir/LargeModeReadingTypographyTest.kt
git commit -m "refactor(android): migrate memoir reading typography to tokens"
```
### Task 6: 改造高频页面与全局扫尾screen 层)
**Files:**
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ProfileScreen.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/screens/CreateMemoryScreen.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ConversationListScreen.kt`
- Modify: `app-android/app/src/main/java/com/huaga/life_echo/ui/screens/*.kt`(其余硬编码清理)
- Test: `app-android/app/src/test/java/com/huaga/life_echo/ui/theme/HardcodedTypographyGuardTest.kt`
**Step 1: Write the failing test**
```kotlin
@Test
fun uiDirectory_shouldNotIntroduceNewHardcodedSpValues() {
val offenders = scanUiFilesForPattern("""\b\d+(\.\d+)?\.sp\b""")
.filterNot { allowedLegacyPattern(it) }
assertTrue("Found hardcoded sp: $offenders", offenders.isEmpty())
}
```
**Step 2: Run test to verify it fails**
Run: `cd app-android && ./gradlew :app:testDebugUnitTest --tests "com.huaga.life_echo.ui.theme.HardcodedTypographyGuardTest" -i`
Expected: FAIL现有硬编码命中
**Step 3: Write minimal implementation**
- 优先处理高频页面(聊天、阅读、设置)
- 然后按文件批次替换其余 screen 层硬编码
- 必要保留项加白名单注释并记录原因
**Step 4: Run test to verify it passes**
Run: same command as Step 2
Expected: PASS或仅剩明确白名单
**Step 5: Commit**
```bash
cd app-android
git add app/src/main/java/com/huaga/life_echo/ui/screens \
app/src/test/java/com/huaga/life_echo/ui/theme/HardcodedTypographyGuardTest.kt
git commit -m "refactor(android): migrate screens to typography and touch tokens"
```
### Task 7: 完整验证与文档更新
**Files:**
- Modify: `app-android/README.md`
- Modify: `docs/plans/2026-03-06-android-large-typography-design.md`(补实现状态)
- Create: `docs/plans/2026-03-06-android-large-typography-test-report.md`
**Step 1: Write the failing verification checklist**
```markdown
- [ ] 聊天页大字模式无气泡截断
- [ ] 阅读页正文行高稳定
- [ ] 设置页列表项可触达 >= 52dp
- [ ] 3 分钟任务可完成
```
**Step 2: Run full test suites**
Run:
- `cd app-android && ./gradlew :app:testDebugUnitTest`
- `cd app-android && ./gradlew :app:connectedDebugAndroidTest`
Expected:
- Unit tests: BUILD SUCCESSFUL
- Android tests: all passed
**Step 3: Run manual DoD scenario**
Run 手工流程并记录:
- 打开大字模式
- 阅读章节
- 返回目录
- 发送消息
- 记录耗时和阅读反馈
**Step 4: Update docs with evidence**
- 在测试报告中写入命令、结果、设备、截图路径、失败重试记录(如有)。
**Step 5: Commit**
```bash
git add app-android/README.md \
docs/plans/2026-03-06-android-large-typography-design.md \
docs/plans/2026-03-06-android-large-typography-test-report.md
git commit -m "docs(android): add large typography migration verification report"
```
---
## Execution Notes
- 每个 Task 完成后先做局部 code review再进入下一 Task。
- 严格保持“测试先行(红)-> 最小实现(绿)-> 重构(保持绿)”。
- 若出现 UI 回归,优先回滚当前 Task 的改动,不跨 Task 修补。
- 完成前必须执行 @superpowers:verification-before-completion。