Files
life-echo/docs/plans/2026-03-06-android-large-typography-implementation.md
iammm0 cca8ce7731 docs: 新增 Android 大字号排版方案文档
新增 Android 大字号排版的设计与实施计划文档,便于跨设备继续开发并保持上下文一致。
2026-03-09 09:08:38 +08:00

16 KiB
Raw Blame History

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

@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)
}
@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

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,
)
data class TouchTargetTokens(
    val buttonMinHeight: Dp,
    val listItemMinHeight: Dp,
    val iconTapMinSize: Dp,
    val inputMinHeight: Dp,
)
@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

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

@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: FAILtoTextStyle() 不存在)

Step 3: Write minimal implementation

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

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

@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

val typography = LocalTypographyTokens.current
val touch = LocalTouchTargetTokens.current

Row(
    modifier = Modifier
        .fillMaxWidth()
        .heightIn(min = touch.listItemMinHeight)
        .clickable(...)
)

SectionCard/AppScaffold 的标题与间距统一改为 tokenMarkdownText 的 paragraph/quote/code 同步走 token阅读页会复用

Step 4: Run test to verify it passes

Run: same command as Step 2
Expected: PASS

Step 5: Commit

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

@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

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

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

@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

Text(
    text = chapter.title,
    style = LocalTypographyTokens.current.readingTitle.toTextStyle()
)
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

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

@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

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

- [ ] 聊天页大字模式无气泡截断
- [ ] 阅读页正文行高稳定
- [ ] 设置页列表项可触达 >= 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

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。