# 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` 的双层 token(Typography + 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: FAIL(token 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。