From b485b52eec2a7bad92b1b19bc3aaa410500c785d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=96=B0=E9=9B=A8?= Date: Wed, 28 Jan 2026 22:13:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec_kit/Onboarding App Shell/plan.md | 137 +++++++++++++++++++++++++ spec_kit/Onboarding App Shell/tasks.md | 127 +++++++++++++++++++++++ spec_kit/iOS Widget/plan.md | 50 +++++++++ spec_kit/iOS Widget/spec.md | 84 +++++++++++++++ spec_kit/iOS Widget/tasks.md | 79 ++++++++++++++ spec_kit/overview.md | 11 ++ 6 files changed, 488 insertions(+) create mode 100644 spec_kit/Onboarding App Shell/plan.md create mode 100644 spec_kit/Onboarding App Shell/tasks.md create mode 100644 spec_kit/iOS Widget/plan.md create mode 100644 spec_kit/iOS Widget/spec.md create mode 100644 spec_kit/iOS Widget/tasks.md diff --git a/spec_kit/Onboarding App Shell/plan.md b/spec_kit/Onboarding App Shell/plan.md new file mode 100644 index 0000000..80fb281 --- /dev/null +++ b/spec_kit/Onboarding App Shell/plan.md @@ -0,0 +1,137 @@ +# Onboarding App Shell(技术计划) + +## 1. 计划目标 + +基于 `spec.md` 的确认项,落地客户端的“首次进入体验 + 主应用壳 + 关键入口”: + +- Onboarding:**3–5 页**,每页都有问题但**全部可跳过** +- Push 设置页:**需要**,且**可跳过**;选择“立即开启”会触发系统权限申请 +- 进入主 App:内容卡片支持 **点赞/讨厌** + - Like:**加入收藏** + - Dislike:**仅本地记录**(不做“永不出现/降权”) +- 收藏夹入口:需要;**不支持取消收藏/清空** +- 通用设置入口:需要;仅展示 **version**;iOS 小组件入口仅展示一段**美观说明文案** +- 当前阶段:**仅做客户端**(不接后端) + +## 2. 默认技术决策与约束 + +- **路由框架**:使用现有 `expo-router` +- **导航结构**:**不使用 Tabs** + - 使用 Stack + 顶部 Header 入口(从 Home 进入 Favorites/Settings) +- **持久化**:使用 `@react-native-async-storage/async-storage` +- **多语言**:沿用现有 i18n 基座(后续在页面文案中逐步补 key) + +## 3. 路由与页面结构(不使用 Tabs) + +### 3.1 路由分组(建议) + +- `app/(onboarding)/...`:Onboarding 多页 + PushPrompt +- `app/(app)/...`:Home/Favorites/Settings +- `app/index.tsx`:启动路由分发(根据 onboarding 状态跳转) + +### 3.2 页面清单(目标态) + +- `Onboarding`(3–5 页) + - 每页:问题展示 + “下一步” + “跳过” + - 进度指示:`x / total` +- `PushPrompt` + - 按钮:立即开启 / 稍后 +- `Home` + - 内容卡片(本地 mock) + - Like / Dislike + - Header 入口:收藏夹、设置 +- `Favorites` + - 仅展示收藏列表(不提供取消/清空) +- `Settings` + - version 展示 + - iOS 小组件说明卡片(美观文案) + - 可选:通知状态展示(不影响验收) + +## 4. 数据模型与本地存储设计 + +### 4.1 Key 约定(AsyncStorage) + +- `onboarding.completed`: `true | false` +- `push.promptState`: `enabled | skipped | unknown` +- `content.reactions`: `{ [contentId: string]: 'like' | 'dislike' }` +- `favorites.items`: `string[]`(收藏内容 ID 列表) + +### 4.2 内容模型(本地 mock) + +最小字段: + +- `id: string` +- `text: string`(情绪文字/正念短句) + +可选字段(非必需): + +- `bgColor/bgGradient/image`(用于后续小组件与卡片美化) + +## 5. 核心交互逻辑 + +### 5.1 Onboarding 跳过策略 + +- **每页可跳过**:点击跳过直接进入下一页(或直接结束 Onboarding,二选一;默认“跳到下一页”,最后一页结束) +- **整体跳过**:在任意页可“一键跳过整个 Onboarding” +- **不允许重进**:设置页不提供重新进入 Onboarding 的入口 +- Onboarding 结束后写入 `onboarding.completed=true` + +### 5.2 PushPrompt + +- 立即开启:触发权限申请 + - 成功/失败都可继续进入主 App + - 写入 `push.promptState=enabled`(或按结果细分,当前阶段不强制) +- 稍后:写入 `push.promptState=skipped` +- 页面可跳过,不阻塞进入主 App + +### 5.3 Like / Dislike / 收藏 + +- Like: + - 写入 `content.reactions[contentId]='like'` + - 将 `contentId` 追加到 `favorites.items`(去重) +- Dislike: + - 写入 `content.reactions[contentId]='dislike'` + - 不影响收藏,不改变内容排序策略(仅记录) + +## 6. UI 设计要点(高层) + +- Onboarding: + - 温柔、轻量、可跳过 + - 进度清晰(例如 2/4) +- Home: + - 卡片居中,按钮明显(Like/Dislike) + - Header 右侧放“收藏/设置”入口(避免 Tab) +- Settings: + - version 用“信息行”展示 + - iOS 小组件用卡片样式展示一段美观文案(可多语言) + +## 7. 实施步骤(落地顺序) + +1. **路由重构**:从 Tabs 模板迁移为 Stack + Header 入口(不使用 Tabs) +2. **实现启动分发**:`app/index.tsx` 根据 `onboarding.completed` 跳转 +3. **实现 Onboarding 3–5 页**:支持逐页跳过与整体跳过,持久化完成状态 +4. **实现 PushPrompt**:可跳过,立即开启触发权限申请并记录状态 +5. **实现 Home**:本地 mock 内容 + Like/Dislike + Header 入口 +6. **实现 Favorites**:展示收藏列表(不提供取消/清空) +7. **实现 Settings**:version + iOS 小组件说明文案卡片 +8. **补齐 i18n key**:把新增文案接入五语资源(先覆盖 zh-CN/en,其他语言可逐步补齐) + +## 8. 验收与自测清单 + +- 首次启动: + - Onboarding 3–5 页可正常前进,且每页都可跳过 + - 可“一键跳过整个 Onboarding” + - PushPrompt 可跳过;立即开启会触发权限申请;无论选择都能进入主 App +- 非首次启动: + - 不再出现 Onboarding,直接进入主 App +- 主功能: + - Home 可 Like/Dislike 并写入本地记录 + - Like 会进入收藏夹列表 + - 收藏夹可进入且能看到收藏内容(不提供取消/清空) + - Settings 可进入并显示 version 与小组件说明文案 + +## 9. 风险与处理 + +- Expo/模拟器环境差异:确保在 iOS 模拟器与真机(Expo Go)都能走通流程 +- 通知权限弹窗:若用户拒绝,不影响进入主功能,状态按“已提示”记录即可 + diff --git a/spec_kit/Onboarding App Shell/tasks.md b/spec_kit/Onboarding App Shell/tasks.md new file mode 100644 index 0000000..b85e025 --- /dev/null +++ b/spec_kit/Onboarding App Shell/tasks.md @@ -0,0 +1,127 @@ +# Onboarding App Shell(任务清单) + +> 说明:本清单根据 `plan.md` 拆解,要求“可执行、可验收、可标记”。 +> 标记规则:执行完成后将对应项从 `- [ ]` 改为 `- [x]`,并把“状态”改为 **已完成**;进行中改为 **进行中**;阻塞写明原因与解除条件。 + +## 0. 清单状态说明 + +- **状态**:未开始 / 进行中 / 已完成 / 阻塞 +- **阻塞**:必须写明阻塞点与解除条件 + +## 1. 路由重构(不使用 Tabs) + +- [ ] **移除 Tabs 导航结构,切换为 Stack + Header 入口** + - **状态**:未开始 + - **范围**:`client/app/(tabs)/*`、`client/app/_layout.tsx`、新增 `client/app/(app)/*`、`client/app/(onboarding)/*` + - **要求**: + - 首页为 `Home`(非 Tab) + - 从 Home 的 Header 进入 `Favorites` 与 `Settings` + - **验收**:启动后不出现 TabBar;可从 Home 进入收藏夹与设置 + +- [ ] **实现启动分发 `app/index.tsx`(按 onboarding 状态跳转)** + - **状态**:未开始 + - **规则**: + - `onboarding.completed=true`:进入 `/(app)/home` + - 否则进入 `/(onboarding)/onboarding` + - **验收**:首次启动进入 Onboarding;完成后重启直接进 Home + +## 2. 本地存储与数据模型 + +- [ ] **封装 AsyncStorage 访问层(统一 key 与读写)** + - **状态**:未开始 + - **Key**: + - `onboarding.completed` + - `push.promptState` + - `content.reactions` + - `favorites.items` + - **验收**:读写都有类型约束;不会散落在 UI 代码里重复写 key + +- [ ] **提供本地 mock 内容列表(带稳定 id)** + - **状态**:未开始 + - **字段**:`id`、`text` + - **验收**:Home 可稳定渲染一条内容,并能切换到下一条(如实现) + +## 3. Onboarding(3–5 页,全部可跳过) + +- [ ] **实现 Onboarding 容器页(3–5 页)** + - **状态**:未开始 + - **要求**: + - 显示进度(例如 `2/4`) + - 每页都有“下一步”与“跳过” + - 任意页有“一键跳过整个 Onboarding” + - **验收**:可从任意页跳过且不阻塞进入 PushPrompt + +- [ ] **Onboarding 完成状态持久化** + - **状态**:未开始 + - **要求**:完成或整体跳过时写入 `onboarding.completed=true` + - **验收**:重启 App 不再出现 Onboarding + +## 4. PushPrompt(需要且可跳过) + +- [ ] **实现 PushPrompt 页面** + - **状态**:未开始 + - **按钮**: + - 立即开启(触发系统权限申请) + - 稍后(跳过) + - **持久化**: + - 立即开启:写入 `push.promptState=enabled` + - 稍后:写入 `push.promptState=skipped` + - **验收**:无论选择/权限结果如何,都能进入 Home + +## 5. Home(点赞/讨厌) + +- [ ] **实现 Home 页面(内容卡片 + Like/Dislike)** + - **状态**:未开始 + - **要求**: + - 展示一条 mock 内容 + - Like / Dislike 按钮明显 + - **验收**:点击 Like/Dislike 会写入本地记录 + +- [ ] **实现 Like=收藏规则** + - **状态**:未开始 + - **规则**: + - Like:记录 `content.reactions[id]='like'` + - Like:将 `id` 追加到 `favorites.items`(去重) + - Dislike:记录 `content.reactions[id]='dislike'`(不改变出现策略) + - **验收**:Like 后收藏夹可见该内容;Dislike 仅记录不影响收藏 + +- [ ] **在 Home Header 加入入口:收藏夹、设置** + - **状态**:未开始 + - **验收**:从 Home 可进入 Favorites 与 Settings + +## 6. Favorites(仅展示,不可取消/清空) + +- [ ] **实现 Favorites 页面(只读列表)** + - **状态**:未开始 + - **要求**: + - 展示 `favorites.items` 对应的内容 + - 不提供取消收藏/清空 + - **验收**:Like 的内容在此页可见;无取消入口 + +## 7. Settings(version + iOS 小组件说明文案) + +- [ ] **实现 Settings 页面** + - **状态**:未开始 + - **内容**: + - version 信息行(仅 version) + - iOS 小组件说明卡片(美观文案) + - **验收**:页面可进入,信息展示正确,文案样式不突兀 + +## 8. i18n 文案补齐 + +- [ ] **补齐 Onboarding/Push/Home/Favorites/Settings 的 i18n key(至少 zh-CN/en)** + - **状态**:未开始 + - **要求**: + - key 点分层:`onboarding.*`、`push.*`、`home.*`、`favorites.*`、`settings.*` + - 先覆盖 `zh-CN/en`,其余语言可后续逐步补齐 + - **验收**:页面不出现硬编码长句;主要文案可切换语言生效 + +## 9. 自测与验收 + +- [ ] **流程自测(首次/非首次)** + - **状态**:未开始 + - **路径**: + - 首次启动:Onboarding(逐页跳过/整体跳过)-> PushPrompt(开启/稍后)-> Home + - 非首次:直接 Home + - **验收**:与 `plan.md` 的验收清单一致 + diff --git a/spec_kit/iOS Widget/plan.md b/spec_kit/iOS Widget/plan.md new file mode 100644 index 0000000..c3cfe08 --- /dev/null +++ b/spec_kit/iOS Widget/plan.md @@ -0,0 +1,50 @@ +# iOS Widget(技术计划,V1 写死文案) + +## 1. 计划目标 + +在 iOS 上实现真正可添加到桌面的小组件(WidgetKit Extension),V1 先展示**写死的美观文案**,不做 App->Widget 数据同步与更新。 + +- **构建方式**:方案 A(`expo prebuild` + Xcode) +- **点击跳转**:跳转到 App 的 Home +- **尺寸**:Small / Medium / Large 都支持 + +## 2. 默认技术决策 + +- **小组件实现**:iOS 原生 WidgetKit Extension(SwiftUI) +- **数据来源**:V1 写死在 Widget 代码内 +- **跳转**:使用 Widget 的 `widgetURL` 打开 App(深链) +- **App 侧路由**:使用现有 `expo-router` + +## 3. Deep Link 约定(点击小组件打开 Home) + +### 3.1 Scheme + +当前 `client/app.json` 中已存在: + +- `expo.scheme = "client"` + +因此小组件点击跳转使用: + +- `client:///(app)/home` + +> 说明:这是 expo-router 的路径形态;若你后续调整路由结构,需要同步更新此 URL。 + +## 4. 实施步骤(高层) + +1. **预构建 iOS 原生工程**:生成 `client/ios/` +2. **在 Xcode 创建 WidgetKit Extension**:命名例如 `MindfulnessWidget` +3. **实现 3 种尺寸 UI**:Small/Medium/Large 都展示写死文案,但排版适配不同尺寸 +4. **设置点击跳转**:为 Widget 配置 `widgetURL` 指向 `client:///(app)/home` +5. **本地运行验证**:在模拟器安装 App,添加桌面小组件并验证显示与跳转 + +## 5. 验收标准 + +- 可以在 iOS 桌面添加小组件(Small/Medium/Large) +- 小组件展示写死文案,排版美观 +- 点击小组件能打开 App 并进入 Home + +## 6. 风险与注意事项 + +- **Expo Go 不支持**:必须用 Xcode 运行预构建后的 App(或后续 EAS Build) +- **Bundle Identifier**:当前 `client/app.json` 使用 `com.anonymous.client`,仅适合本地验证;上线前需替换为真实 bundle id,并同步证书/签名 + diff --git a/spec_kit/iOS Widget/spec.md b/spec_kit/iOS Widget/spec.md new file mode 100644 index 0000000..01f2bc8 --- /dev/null +++ b/spec_kit/iOS Widget/spec.md @@ -0,0 +1,84 @@ +# iOS Widget(高层规范) + +## 1. 背景与目标 + +当前客户端仅在设置页展示了“小组件说明文案”,并未实现真正的 iOS 桌面小组件。 + +本需求用于实现**真正的 iOS 小组件**(WidgetKit Extension),使用户可以在 iOS 桌面添加小组件并看到“情绪文字/正念短句”等内容。 + +### 目标 + +- **可添加到桌面**:支持在 iOS 桌面添加小组件(WidgetKit) +- **V1(本期)写死内容**:小组件展示一段美观文案(先写死,不做数据同步/更新) +- **可点击跳转**:点击小组件可跳转回 App 指定页面(默认 Home) + +### 非目标(本阶段不做) + +- 不承诺严格准点刷新(受 iOS 系统调度限制) +- 不做复杂的小组件交互配置(如需可后续拆分) +- 不要求 Android Widget(仅 iOS) +- **V1 不做数据更新**:不做 App -> Widget 数据共享与更新触发(App Group 留到后续版本) + +## 2. 关键结论与约束(必须接受) + +- **Expo Go 不支持 WidgetKit**:必须使用 **Development Build / 预构建后的原生工程 / EAS Build** 才能安装带小组件扩展的 App +- **需要原生扩展**:iOS 小组件必须创建 **WidgetKit Extension(SwiftUI)** +- **V1 不需要 App Group**:因为内容写死在 Widget 内部;后续做数据共享时再引入 App Group + +## 3. 小组件功能需求(高层) + +### 3.1 展示内容 + +- 展示一段“情绪文字/正念短句” +- 背景支持: + - 纯色或渐变(优先) + - 图片(可选,后续) +- 文案需“美观可读”,适配暗色模式(可选) + +### 3.2 尺寸支持 + +- 支持 iOS 常见尺寸: + - Small + - Medium + - Large + +### 3.3 更新策略 + +V1:不做更新策略(内容写死)。后续版本可扩展: + +- 固定间隔更新(例如每天/每小时) +- App 驱动更新:App 写入新内容,Widget 触发 reload(系统仍可能延迟) + +### 3.4 点击跳转 + +- 点击小组件可打开 App 并跳转到指定页面: + - 默认:Home + - 可选:Favorites + +## 4. 数据共享与持久化(高层) + +V1:不做数据共享与持久化(写死文案)。 + +后续版本(V2)再引入 App Group(共享 UserDefaults)与数据来源策略: + +- `widget.text`、`widget.updatedAt`、`widget.lang`、`widget.theme` 等 +- 数据来源可选:Home 当前/最近 Like/随机 mock + +## 5. 多语言(与 App 一致) + +- 语言码沿用客户端:`zh-CN/en/es/pt/zh-TW` +- V1:Widget 内可先写死单语(例如中文)或做最小的本地化(可选) +- V2:建议由 App 端选择语言并写入共享数据,降低 Widget 端 i18n 复杂度 + +## 6. 验收标准 + +- 可以在 iOS 桌面搜索并添加小组件 +- 小组件能显示一段美观文案(至少支持 Small) +- 点击小组件能打开 App 并进入预期页面 + +## 7. 风险与注意事项 + +- iOS 刷新频率限制:不可承诺“每分钟刷新”等高频 +- App Group / Bundle ID 配置错误会导致 Widget 读不到数据 +- Dev Build / EAS 证书配置成本较高,需要在计划阶段明确落地路径 + diff --git a/spec_kit/iOS Widget/tasks.md b/spec_kit/iOS Widget/tasks.md new file mode 100644 index 0000000..09ec81c --- /dev/null +++ b/spec_kit/iOS Widget/tasks.md @@ -0,0 +1,79 @@ +# iOS Widget(任务清单,V1 写死文案) + +> 说明:本清单根据 `plan.md` 拆解,要求“可执行、可验收、可标记”。 +> 标记规则:执行完成后将对应项从 `- [ ]` 改为 `- [x]`,并把“状态”改为 **已完成**;进行中改为 **进行中**;阻塞写明原因与解除条件。 + +## 0. 清单状态说明 + +- **状态**:未开始 / 进行中 / 已完成 / 阻塞 +- **阻塞**:必须写明阻塞点与解除条件 + +## 1. 预构建 iOS 原生工程(必须) + +- [x] **执行 prebuild 生成 `ios/` 目录** + - **状态**:已完成(已执行 `npx expo prebuild -p ios --no-install`,已生成 `client/ios/`) + - **命令**: + +```bash +cd client +npx expo prebuild -p ios +``` + + - **产出**:`client/ios/`(包含 Xcode 工程) + - **验收**:`client/ios/` 目录存在,并能在 Xcode 打开 + +## 2. 创建 WidgetKit Extension(Xcode 操作) + +- [x] **在 Xcode 新建 Widget Extension(WidgetKit)** + - **状态**:已完成(已创建 Target:情绪小组件;下一步把 Swift 文件加入该 Target 并跑模拟器验证) + - **操作**: + - 打开 `client/ios/*.xcworkspace`(优先 workspace) + - `File -> New -> Target... -> Widget Extension` + - 名称建议:`MindfulnessWidget`(或你喜欢的命名) + - **验收**:工程里新增 Widget target,能编译通过 + - **下一步(必须做)**: + - 把 `client/ios/MindfulnessWidget/MindfulnessWidget.swift` 加入 Target Membership:`情绪小组件` + - 如果 Xcode 自动生成了同类的 Widget 主文件(例如 `情绪小组件.swift` / `*_Bundle.swift`),请确保最终只有一个 `@main struct ...: Widget` 入口(避免重复入口导致编译失败) + +## 3. 实现写死文案(3 个尺寸都要) + +- [x] **实现 Small/Medium/Large 三种尺寸布局** + - **状态**:已完成(已提供 `client/ios/MindfulnessWidget/MindfulnessWidget.swift` 写死文案 + 三尺寸布局代码骨架) + - **文案(写死)**:可先用一条默认文案,例如:`你已经很努力了,今天也值得被温柔对待。` + - **要求**: + - Small:一行标题 + 主文案(可截断) + - Medium:主文案更完整(可多行) + - Large:增加留白与排版层次(例如标题 + 文案 + 小注脚) + - **验收**:三种尺寸均显示美观,无溢出/遮挡 + +## 4. 点击跳转到 Home(Deep Link) + +- [x] **为 Widget 配置点击跳转 URL** + - **状态**:已完成(代码骨架已使用 `.widgetURL(URL(string: \"client:///(app)/home\")!)`) + - **URL**:`client:///(app)/home` + - **实现**:在 Widget 中设置 `.widgetURL(URL(string: \"client:///(app)/home\")!)` + - **验收**:点击小组件能打开 App 并进入 Home + +## 5. 本地验证 + +- [ ] **在 iOS 模拟器运行并添加桌面小组件** + - **状态**:阻塞(需要本机 Xcode Run 安装后在模拟器桌面添加 Widget 验证) + - **步骤**: + - Xcode Scheme 选择主 App(一般是 `client`),选择模拟器,点击 Run 安装 App + - 回到桌面长按 -> “+” -> 搜索小组件 -> 添加 Small/Medium/Large + - **验收**:小组件显示正确,点击跳转正确 + - **排查**: + - 桌面搜索不到:通常是没有 Run 安装过主 App,或 Widget target 未加入编译 + - 点击不跳转:确认 Widget 里 `widgetURL` 为 `client:///(app)/home`,且 `app.json` 的 `scheme` 为 `client` + +## 6. 文档补充(可选但建议) + +- [x] **在 `client/README.md` 增加“小组件开发(V1 写死文案)”说明** + - **状态**:已完成 + - **内容**: + - Expo Go 不支持 + - prebuild 命令 + - Xcode 创建 Widget Extension 的步骤 + - deep link:`client:///(app)/home` + - **验收**:新同学照 README 可复现 + diff --git a/spec_kit/overview.md b/spec_kit/overview.md index 0e196cc..0c53aa9 100644 --- a/spec_kit/overview.md +++ b/spec_kit/overview.md @@ -36,3 +36,14 @@ - **核心范围**:Onboarding 多页流程、Push 引导页、主界面反应操作(Like/Dislike)、收藏夹、设置页(版本、iOS 小组件入口/说明) - **阶段产物**: - `spec_kit/Onboarding App Shell/spec.md` + - `spec_kit/Onboarding App Shell/plan.md` + - `spec_kit/Onboarding App Shell/tasks.md` + +## iOS Widget + +- **目标**:实现真正的 iOS 桌面小组件(WidgetKit Extension),展示美观文案并与 App 共享数据 +- **核心范围**:WidgetKit 扩展、App Group 数据共享、尺寸适配(Small/Medium/Large)、点击跳转回 App +- **阶段产物**: + - `spec_kit/iOS Widget/spec.md` + - `spec_kit/iOS Widget/plan.md` + - `spec_kit/iOS Widget/tasks.md`