Files
mindfulness/spec_kit/Card UI/plan.md
2026-01-29 23:31:12 +08:00

6.6 KiB
Raw Blame History

Card UI技术计划

1. 计划目标

在卡片页(/(app)/home)完成 UI 与交互升级:

  • 右上角两个圆形图标按钮:
    • 主题切换(theme.svg)→ 打开主题弹窗
    • 个人主页(my.svg)→ 打开个人主页弹窗
  • 两个弹窗统一视觉规范:弹窗内容区背景色 #FAF3EC、圆角、遮罩、关闭交互一致
  • 卡片页底部「讨厌/喜欢」从文字按钮改为图标按钮(hate.svglike.svg),点击有动效

2. 默认技术决策

  • 路由:沿用 expo-router(不新增 modal route弹窗优先用页面内 Modal 承载,降低路由复杂度)
  • 动效:优先使用已存在的 react-native-reanimated(按压缩放/回弹等)
  • SVG 渲染:使用 react-native-svg + 新增 react-native-svg-transformer,让本地 .svg 可直接 import 为组件
  • 持久化:沿用 AsyncStorage(在 src/storage/appStorage.ts 统一封装 key

3. 资源与命名约定

图标资源位置:client/assets/images/home/

  • theme.svg:主题入口
  • my.svg:个人主页入口
  • like.svg:喜欢
  • hate.svg:讨厌(逻辑层仍沿用 reaction=dislike,仅资源命名为 hate

4. UI 与交互实现设计

4.1 右上角双按钮(主题 / 我的)

落点:client/app/(app)/_layout.tsx

  • homeheaderRight 改为两个 icon 按钮容器(水平排列)
  • 统一按钮样式:圆形底、浅色背景、hitSlop>=10
  • 点击行为:
    • 主题按钮:触发 Home 页打开「主题弹窗」
    • 我的按钮:触发 Home 页打开「个人主页弹窗」

实现方式:headerRight 只负责“发出事件”,弹窗的实际 UI 放在 home.tsx 内(避免把复杂 UI 塞进导航 header

事件方案(两种可选,优先 A

  • A使用一个轻量全局状态例如在 home 内通过 useFocusEffect + router 事件不太合适)
    • 更推荐:把 header 做成 Home 页内部自绘顶部栏(headerShown:false),所有状态都留在页面内
  • B继续用导航 header但通过 React.Context 或一个简易 store如 Zustand若项目未引入则先不加把“打开弹窗”动作传给页面

本计划默认采用 AHome 页自绘顶部栏),因为实现最直接、样式可控、无跨层状态传递。

4.2 主题弹窗(背景色 #FAF3EC

落点建议:

  • client/components/home/ThemeModal.tsx
  • client/components/ui/SheetModal.tsx(复用:遮罩、圆角容器、上滑/淡入动效、关闭逻辑)

交互:

  • 点击主题按钮 → 打开弹窗
  • 选择「风景 / 颜色」→ 立即写入主题 mode + 关闭(或保持打开并高亮选中,按设计稿)
  • 点击遮罩 / 关闭按钮 → 关闭

视觉:

  • 弹窗内容区背景:#FAF3EC
  • 容器圆角与内边距按设计稿(默认 24 圆角、1620 padding

4.3 个人主页弹窗(背景色 #FAF3EC

落点建议:

  • client/components/home/ProfileModal.tsx
  • 复用 SheetModal

内容与导航:

  • 顶部:插画/头像 + 昵称(来自 getUserProfile().name,无则显示占位名)
  • 快捷卡片:我的喜欢 / 小组件(按现有设计稿与已有页面映射)
  • 通用列表:每日提醒 / 隐私政策 / 使用条款 / 语言

页面/能力映射(一期最小闭环):

  • 我的喜欢 → 跳转到 /(app)/favorites
  • 语言 → 跳转到现有语言设置页(若尚未落地,则在本次实现中补齐入口或先占位)
  • 隐私政策 / 使用条款 → 复用 Splash Consent 的外链打开方式(或后续统一到一个 WebView 组件)
  • 每日提醒 → 若现有页面未实现,先占位并在 tasks 阶段拆分

4.4 喜欢 / 讨厌按钮改为 icon + 动效

落点:client/app/(app)/home.tsx

  • 将底部两个 Pressable + Text 按钮替换为 icon 按钮:
    • 喜欢:like.svg
    • 讨厌:hate.svg
  • 点击动效(建议):
    • pressInscale 到 0.92
    • pressOut:回弹到 1
    • onPress:短促 “弹一下” 的强调动效(例如 scale 1 → 1.12 → 1随后再推进下一张卡片

动效仅作用于按钮本体即可先满足“点击有动效”,后续可扩展为卡片飞出/淡出等更强反馈。

5. 主题状态与持久化

落点:client/src/storage/appStorage.ts

新增 keyspec.md 保持一致):

  • ui.theme.mode: 'scenery' | 'color'

新增封装方法(示例命名):

  • getThemeMode(): Promise<'scenery' | 'color'>(默认 scenery
  • setThemeMode(mode: 'scenery' | 'color'): Promise<void>

Home 页读取主题 mode并用于

  • 页面背景(风景/颜色)
  • 卡片背景与局部配色(按设计稿映射)

6. SVG 导入支持(工程配置)

由于项目当前未见 metro.config.js,计划新增 SVG transformer 支持:

  1. 安装依赖:react-native-svg-transformer
  2. 新增 client/metro.config.js,基于 expo/metro-config 配置:
    • svg 从 assetExts 移到 sourceExts
    • babelTransformerPath 指向 transformer

完成后可以直接:

  • import LikeIcon from '@/assets/images/home/like.svg'

7. i18n 文案补齐(用于标题/无障碍)

建议新增 key至少 zh-CN/en,其余语言可后续补齐):

  • home.theme(用于无障碍 label
  • home.profile(用于无障碍 label
  • theme.titletheme.scenerytheme.color
  • profile.titleprofile.favoritesprofile.widgetprofile.dailyReminderprofile.privacyprofile.termsprofile.language

原有 home.like/home.dislike 即使不再显示文字,也可保留用于 accessibilityLabel 或未来回退。

8. 实施步骤

  1. 配置 SVG 导入(新增 transformer + metro.config.js
  2. 新增通用弹窗组件 SheetModal(遮罩、圆角、背景色 #FAF3EC、关闭交互、基础动效)
  3. 新增 ThemeModalProfileModal(复用 SheetModal
  4. appStorage.ts 新增主题 mode 读写方法
  5. 改造 home.tsx
    • 自绘顶部栏(右上角两个圆形 icon 按钮)
    • 接入主题 mode 影响背景
    • 底部喜欢/讨厌替换为 icon + 动效
  6. 补齐 i18n key
  7. 自测:
    • 主题切换后即时生效并可持久化
    • 个人主页弹窗打开/关闭体验一致
    • 喜欢/讨厌点击有动效且逻辑正常推进卡片

9. 验收标准

  • 卡片页右上角两个 icon 按钮显示正确,点击热区合理
  • 主题弹窗与个人主页弹窗:
    • 背景色 #FAF3EC
    • 遮罩/圆角/关闭交互一致
  • 喜欢/讨厌按钮为 iconlike.svg / hate.svg)且点击有动效
  • 主题选择被持久化,重启 App 后仍保持上次主题