# Card UI(技术计划) ## 1. 计划目标 在卡片页(`/(app)/home`)完成 UI 与交互升级: - 右上角两个圆形图标按钮: - 主题切换(`theme.svg`)→ 打开主题弹窗 - 个人主页(`my.svg`)→ 打开个人主页弹窗 - 两个弹窗统一视觉规范:**弹窗内容区背景色 `#FAF3EC`**、圆角、遮罩、关闭交互一致 - 卡片页底部「讨厌/喜欢」从文字按钮改为图标按钮(`hate.svg`、`like.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` - 将 `home` 的 `headerRight` 改为两个 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,若项目未引入则先不加)把“打开弹窗”动作传给页面 本计划默认采用 **A(Home 页自绘顶部栏)**,因为实现最直接、样式可控、无跨层状态传递。 ### 4.2 主题弹窗(背景色 #FAF3EC) 落点建议: - `client/components/home/ThemeModal.tsx` - `client/components/ui/SheetModal.tsx`(复用:遮罩、圆角容器、上滑/淡入动效、关闭逻辑) 交互: - 点击主题按钮 → 打开弹窗 - 选择「风景 / 颜色」→ 立即写入主题 mode + 关闭(或保持打开并高亮选中,按设计稿) - 点击遮罩 / 关闭按钮 → 关闭 视觉: - 弹窗内容区背景:`#FAF3EC` - 容器圆角与内边距按设计稿(默认 24 圆角、16–20 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` - 点击动效(建议): - `pressIn`:scale 到 0.92 - `pressOut`:回弹到 1 - `onPress`:短促 “弹一下” 的强调动效(例如 scale 1 → 1.12 → 1),随后再推进下一张卡片 > 动效仅作用于按钮本体即可先满足“点击有动效”,后续可扩展为卡片飞出/淡出等更强反馈。 ## 5. 主题状态与持久化 落点:`client/src/storage/appStorage.ts` 新增 key(与 `spec.md` 保持一致): - `ui.theme.mode`: `'scenery' | 'color'` 新增封装方法(示例命名): - `getThemeMode(): Promise<'scenery' | 'color'>`(默认 `scenery`) - `setThemeMode(mode: 'scenery' | 'color'): Promise` 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.title`、`theme.scenery`、`theme.color` - `profile.title`、`profile.favorites`、`profile.widget`、`profile.dailyReminder`、`profile.privacy`、`profile.terms`、`profile.language` > 原有 `home.like/home.dislike` 即使不再显示文字,也可保留用于 `accessibilityLabel` 或未来回退。 ## 8. 实施步骤 1. 配置 SVG 导入(新增 transformer + `metro.config.js`) 2. 新增通用弹窗组件 `SheetModal`(遮罩、圆角、背景色 `#FAF3EC`、关闭交互、基础动效) 3. 新增 `ThemeModal` 与 `ProfileModal`(复用 `SheetModal`) 4. 在 `appStorage.ts` 新增主题 mode 读写方法 5. 改造 `home.tsx`: - 自绘顶部栏(右上角两个圆形 icon 按钮) - 接入主题 mode 影响背景 - 底部喜欢/讨厌替换为 icon + 动效 6. 补齐 i18n key 7. 自测: - 主题切换后即时生效并可持久化 - 个人主页弹窗打开/关闭体验一致 - 喜欢/讨厌点击有动效且逻辑正常推进卡片 ## 9. 验收标准 - 卡片页右上角两个 icon 按钮显示正确,点击热区合理 - 主题弹窗与个人主页弹窗: - 背景色 `#FAF3EC` - 遮罩/圆角/关闭交互一致 - 喜欢/讨厌按钮为 icon(`like.svg` / `hate.svg`)且点击有动效 - 主题选择被持久化,重启 App 后仍保持上次主题