diff --git a/.gitea/workflows/online-deploy.yml b/.gitea/workflows/online-deploy.yml index e340484..b6a7c16 100644 --- a/.gitea/workflows/online-deploy.yml +++ b/.gitea/workflows/online-deploy.yml @@ -27,7 +27,7 @@ jobs: rm -rf repo git clone "${REPO_URL}" repo - - name: Copy backend to /data/wildgrowth/weizhuozhongzhi-ai + - name: Copy backend to /data/wildgrowth/backend run: | set -e cd repo diff --git a/backend/deploy/setup-apple-secret.sh b/backend/deploy/setup-apple-secret.sh index cd69eba..fab2fad 100755 --- a/backend/deploy/setup-apple-secret.sh +++ b/backend/deploy/setup-apple-secret.sh @@ -27,7 +27,7 @@ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no "$SERVER" << EOF echo "✅ 已更新 APPLE_SHARED_SECRET" else echo "APPLE_SHARED_SECRET=$SECRET" >> .env - echo "✅ 已添加 APPLE_SHARED_SECRET" + echo "✅ 已添加 " fi # 验证配置 diff --git a/backend/env.online b/backend/env.online index cc10601..1fffeb1 100644 --- a/backend/env.online +++ b/backend/env.online @@ -1,3 +1,21 @@ +#后端启动端口号配置 +PORT=3005 + +#数据库配置 DATABASE_URL=postgresql://wildgrowth_rw:olCRbB9EKrMEfb@localhost:5432/wildgrowth?schema=public -JWT_SECRET=IZLHw83LLhlmeia2HjolCRbB9EKrMEfb -PORT=3005 \ No newline at end of file + +# 文件配置 +MAX_FILE_SIZE=2097152 + +# 登录配置 +JWT_SECRET=wild-growth-dev-secret-key-change-in-production +JWT_EXPIRES_IN=7d + +# 豆包 API +DOUBAO_API_KEY=a3e13a85-437f-448c-aaa9-14292cd5e0ab + +# 阿里云号码认证 +ALIYUN_ACCESS_KEY_ID=LTAI5tQRdJ76RKA8Eoz2kWG4 +ALIYUN_ACCESS_KEY_SECRET=ymgGigzo3OA10wIapNdCwxjbK6zdVn +ALIYUN_PHONE_VERIFY_SIGN_NAME=速通互联验证码 +ALIYUN_PHONE_VERIFY_TEMPLATE_CODE=100001 \ No newline at end of file diff --git a/ios/WildGrowth/COMPLETION_LogicFixed版_审查报告.md b/ios/WildGrowth/COMPLETION_LogicFixed版_审查报告.md deleted file mode 100644 index 34d5440..0000000 --- a/ios/WildGrowth/COMPLETION_LogicFixed版_审查报告.md +++ /dev/null @@ -1,50 +0,0 @@ -# 「Logic Fixed」版 CompletionView 审查报告(禁止应用) - -**审查对象**:Gemini 提供的 Cyber Polaroid - Logic Fixed 版(内部持有 UserManager、点击拉取后端、参数仅 courseId/courseTitle/path)。 -**结论**:仅审查,**不应用、不修改**仓库内任何文件。 - ---- - -## 一、逻辑层与需求符合性 - -| 项目 | 要求 | 本版实现 | 结论 | -|------|------|----------|------| -| **点击按钮时拉后端** | 用户点击后,在 CompletionView 内部调接口拉最新数据 | `fetchAndDevelop()` 内 `try await userManager.fetchUserProfile()`,再显影展示 | ✅ 符合 | -| **数据权责在 CompletionView** | 不依赖父视图传入 completedLessonCount / focusMinutes | 仅入参:courseId, courseTitle, navigationPath;展示用 `userManager.studyStats.lessons`、`userManager.studyStats.time` | ✅ 符合 | -| **回到我的内容** | 必须调用 navStore.switchToGrowthTab() | `handleBackToContent()` 内先 `navStore.switchToGrowthTab()`,再清 path 或 dismiss | ✅ 符合 | -| **前端持久化动画状态** | 已显影过则直接展示结果,不重复播动画 | UserDefaults `has_revealed_course_\(courseId)`,onAppear 时 `checkDevelopmentStatus()` | ✅ 符合 | - ---- - -## 二、接口与调用方影响 - -| 项目 | 说明 | -|------|------| -| **CompletionView 入参** | courseId, courseTitle, navigationPath? — 与当前仓库中 VerticalScreenPlayerView 的调用一致,**无新增必选参数**。 | -| **VerticalScreenPlayerView** | 无需修改,现有 `CompletionView(courseId:, courseTitle:, navigationPath:)` 可直接编译。 | -| **其他页面** | 无其他调用处,逻辑与展示不受影响。 | - -结论:**仅替换 CompletionView.swift 即可,无需改其他文件。** - ---- - -## 三、实现细节核对 - -| 项目 | 说明 | -|------|------| -| **UserManager.studyStats** | 仓库中为 `(time: Int, lessons: Int)`,本版使用 `userManager.studyStats.lessons`、`userManager.studyStats.time`,字段一致。 | -| **拉取失败时** | `fetchUserProfile()` 抛错时 catch 仅 print,仍执行 `MainActor.run { isDeveloped = true; ... }`,即失败也显影并展示当前 userManager 数据,按钮不会一直 Loading,行为合理。 | -| **0.8s 延时** | 显影前 `Task.sleep(0.8s)` 为体验延时,与「先拉接口再显影」不冲突(实际应在 fetch 完成后显影,当前顺序为:sleep → fetch → 显影)。若希望「拉完再显影、无固定延时」,可去掉 sleep 或改为仅在实际请求完成后显影。 | - ---- - -## 四、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **逻辑层** | 点击拉后端、数据来自 UserManager、不新增父视图业务参数,符合「不改逻辑层」的约定。 | -| **接口** | 仅 courseId / courseTitle / navigationPath,与现有一致,调用方零改动。 | -| **其他页面** | 不受影响。 | -| **建议** | 逻辑与接口均可接受;若采用,仅全量替换 CompletionView.swift。0.8s 延时可视产品需求保留或去掉。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_MagicCard版_审查报告.md b/ios/WildGrowth/COMPLETION_MagicCard版_审查报告.md deleted file mode 100644 index 15682a9..0000000 --- a/ios/WildGrowth/COMPLETION_MagicCard版_审查报告.md +++ /dev/null @@ -1,96 +0,0 @@ -# CompletionView Magic Card 版 — 审查报告(不应用) - -**审查日期**:2025-01-29 -**范围**:Magic Card Edition(粒子庆祝 + 按钮卡片一体化 + 极光光晕) -**结论**:仅审查、不修改仓库;逻辑继承正确,**ParticleModifier 存在两处需修复的实现问题**。 - ---- - -## 1. 需求落实情况 - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **按钮卡片一体化** | 按钮在卡片内,点击后「献祭」化作彩带和数据 | IdleActionView 中按钮,切换后显示 ActiveDashboard + ConfettiExplosion | ✅ | -| **粒子爆炸** | 蓝/紫/粉色粒子炸开 | ConfettiExplosion:Circle + Capsule,confettiColors | ✅ | -| **呼吸光晕** | 激活后卡片背景极淡极光渐变 | AngularGradient + blur(20),isSystemOn 时显示 | ✅ | -| **极简流程** | 点击上传 → 按钮消失 → 数据呈现,无「已同步」占位 | 符合 | ✅ | -| **色彩规范** | 按钮 System Blue | brandBlue = Color.blue | ✅ | - ---- - -## 2. 逻辑继承 - -| 项目 | 结论 | -|------|------| -| **持久化 Key** | ✅ `has_revealed_course_\(courseId)` | -| **游客短路** | ✅ 0.5s 延迟,`performActivation(0, 0)`,不调网络 | -| **登录用户** | ✅ 0.8s 延迟 → fetchUserProfile → performActivation | -| **checkSystemStatus** | ✅ 已激活时恢复最终值 | -| **数字滚动** | ✅ `withAnimation(.linear(1.5))` + RollingNumberText | -| **ScaleButtonStyle** | 未使用,无重复定义风险 | - ---- - -## 3. ⚠️ ParticleModifier 需修复项 - -### 3.1 角度单位错误(影响粒子方向) - -**问题**:`angle` 为角度制 (0~360°),但 `cos(angle)`、`sin(angle)` 在 Swift 中接收弧度制。未做转换会导致粒子方向错误。 - -**修复**:先转换为弧度再计算: -```swift -let rad = angle * .pi / 180 -offset(x: time == 0 ? 0 : cos(rad) * distance, - y: time == 0 ? 0 : sin(rad) * distance) -``` - -### 3.2 angle / distance 每次重算导致动画抖动 - -**问题**:`var angle: Double { Double.random(in: 0...360) }` 和 `var distance: Double { ... }` 为计算属性,`body` 每次求值都会得到新随机数,粒子目标位置不断变化,动画会抖动。 - -**修复**:在 `init` 中一次性随机,并用 `@State` 保存,例如: -```swift -let index: Int -@State private var time: Double = 0.0 -private let angle: Double -private let distance: Double - -init(index: Int) { - self.index = index - self.angle = Double.random(in: 0..<360) - self.distance = Double.random(in: 80...150) - // 如需在 init 中初始化 @State,可用 _time = State(initialValue: 0) -} -``` -并在 `body` 中用 `angle`、`distance` 计算 offset(配合上述弧度转换)。 - ---- - -## 4. ConfettiExplosion 可选优化 - -- **`confettiColors.randomElement()!`**:在 `body` 中每次重绘都会重新随机,粒子颜色可能闪变。可按 `index` 固定颜色,如 `confettiColors[i % confettiColors.count]`,以保持每个粒子颜色稳定。 -- **ForEach(0..<15)**:若 Swift 版本要求,可为 `ForEach` 增加 `id: \.self`。 - ---- - -## 5. 接口与影响范围 - -| 项目 | 结论 | -|------|------| -| **初始化参数** | 三参数不变 | -| **替换方式** | 整文件替换 `CompletionView.swift` | -| **新增类型** | `ParticleModifier` 仅在本文件使用,无符号冲突 | -| **RollingNumberText** | 同前,保留在本文件 | - ---- - -## 6. 总结 - -| 维度 | 结论 | -|------|------| -| **需求落实** | ✅ 卡片一体化、粒子、光晕、流程、色彩均符合 | -| **逻辑继承** | ✅ Key、游客、登录用户、数字滚动正确 | -| **ParticleModifier** | ⚠️ **需修复**:弧度转换 + angle/distance 稳定化 | -| **接口兼容** | ✅ 可直接替换 | - -**审查结论**: Magic Card 版在设计与逻辑上正确,应用前需修复 ParticleModifier 的角度单位与随机值稳定性,否则粒子动画会出现方向错误和抖动。**本次未对仓库做任何修改。** diff --git a/ios/WildGrowth/COMPLETION_NAVIGATION_审查报告.md b/ios/WildGrowth/COMPLETION_NAVIGATION_审查报告.md deleted file mode 100644 index 8b16d47..0000000 --- a/ios/WildGrowth/COMPLETION_NAVIGATION_审查报告.md +++ /dev/null @@ -1,133 +0,0 @@ -# 完成页导航方案 — 审查报告 - -**审查对象**:基于「零副作用 / SSOT / 响应式导航 / 原生手感」的完成页与播放器导航改造方案 -**审查结论**:只做审查,不修改代码;本报告供决策与后续实现参考。 - ---- - -## 一、方案目标与标准(引用) - -| 标准 | 含义 | -|-----|------| -| **零副作用** | 不引入「假页面」污染数据源,无 Hack | -| **单一事实来源 (SSOT)** | 数据源 `allCourseNodes` 仅含真实课程章节 | -| **响应式导航** | 用 `NavigationPath` 堆栈精确控制流向,消除白屏回退 | -| **原生手感** | 用手势识别(非页面索引)触发跳转 | - ---- - -## 二、方案要点摘要 - -1. **删除 CompletionPlaceholderPage 的导航职责**(或整页删除,视实现而定) - - 若保留:仅作纯视觉占位,无 `onAppear` 等导航逻辑。 - -2. **VerticalScreenPlayerView** - - 在**最后一节**挂载「边缘左滑手势」修饰符(如 `LastPageSwipeModifier`)。 - - 左滑触发:`navigationPath.append(CourseNavigation.completion(...))`,或先设 `currentNodeId = "wg://completion"` 再由现有 `onChange` 统一 push。 - - TabView 仅渲染真实 `allCourseNodes`,不把「完成」当作一节数据。 - -3. **CompletionView** - - 新增 `@Binding var navigationPath: NavigationPath`。 - - 「回到我的内容」/「继续学习」调用 `handleReturnToMap()`:`navigationPath.removeLast(2)`,一次 pop 完成页 + 播放器,直接回地图。 - -4. **调用方** - - GrowthView / ProfileView / DiscoveryView 的 `.completion` 分支向 `CompletionView` 传入对应 path 的 `Binding`(如 `$navStore.growthPath` 等)。 - ---- - -## 三、与当前实现的对照 - -| 维度 | 当前实现 | 方案 | -|------|----------|------| -| **数据源** | `allCourseNodes` 纯净;TabView 多一页 `CompletionPlaceholderPage` 用 tag `"wg://completion"`,**未**写入 `allCourseNodes` | 与当前一致或更进一步:完全移除占位页,仅手势触发 push | -| **进入完成页** | 左滑到占位页 → `onChange(of: currentNodeId)` → 0.1s 后 append `.completion` | 最后一节左滑手势 → 直接 append 或先切 tag 再 append,不依赖「多一页」 | -| **完成页返回** | `navStore.switchToGrowthTab()` + `dismiss()`,先回播放器再靠系统/逻辑 | `removeLast(2)` 穿透回地图,无中间层 | -| **占位页** | 存在,纯视觉 + 父视图 `onChange` 驱动 push,无占位内 `onAppear` | 方案 A:保留为纯视觉;方案 B:删除,仅手势 | - -**结论**:方案在「单一事实来源」和「响应式导航」上比当前更彻底;当前已避免在数据源里掺假节点,但仍依赖 TabView 多一页占位。 - ---- - -## 四、按标准的符合度 - -### 4.1 零副作用 - -- **方案**:若不保留占位页,则无「假页」;若保留占位页且仅视觉、无逻辑,则也算零逻辑副作用。 -- **当前**:占位页无 `onAppear` 导航,副作用已收敛,但 TabView 仍多一个「虚拟页」。 -- **符合度**:方案完全符合;当前基本符合,方案更干净。 - -### 4.2 单一事实来源 (SSOT) - -- **方案**:`allCourseNodes` 仅课程章节;完成页由导航栈 + 手势驱动,不进入数据模型。 -- **当前**:`allCourseNodes` 已是纯净的 `flatMap` 章节节点,未追加 placeholder node。 -- **符合度**:方案与当前都符合;方案在视图层也不再依赖「多一页」的 tag,SSOT 更纯粹。 - -### 4.3 响应式导航 - -- **方案**:`CompletionView` 通过 `Binding` 执行 `removeLast(2)`,栈由 path 唯一决定,无 `switchToGrowthTab()` + `dismiss()` 的二次操作。 -- **当前**:依赖 `dismiss()` 回播放器,再从播放器回地图,存在中间层与潜在白屏/闪烁。 -- **符合度**:方案明显更符合;当前有改进空间。 - -### 4.4 原生手感 - -- **方案**:最后一节用 `DragGesture`(或类似)识别左滑,不依赖「滑到下一 tab 索引」才触发。 -- **当前**:依赖用户滑到「占位页」才触发,仍与 TabView 索引/选页绑定。 -- **符合度**:方案更贴近「手势驱动」;当前是「页面索引 + 手势」混合。 - ---- - -## 五、与既有文档的一致性 - -### 5.1 COMPLETION_VIEW_UI_LAYER_SPEC.md - -- 说明要求:返回用 `dismiss()`,**不**接收或操作 `NavigationPath`。 -- **方案**:改为接收 `Binding` 并 `removeLast(2)`,与当前 spec 冲突。 -- **建议**:若采用方案,需**同步更新** COMPLETION_VIEW_UI_LAYER_SPEC: - - 写明「穿透式返回」为可选/推荐实现; - - 接口增加 `@Binding var navigationPath: NavigationPath`; - - 底部按钮行为改为「可调用 `removeLast(2)` 直接回地图」,并注明调用方需传入对应 path 的 Binding。 - -### 5.2 COMPLETION_PAGE_MEANING.md - -- 当前描述为:最后一节后再左滑一页进入占位页,占位页触发 push。 -- **方案**:最后一节左滑即触发(或先切 tag 再触发),可完全去掉「多一页」概念。 -- **建议**:若采用方案,更新 COMPLETION_PAGE_MEANING: - - 「最后一页」仍指课程的最后一节; - - 进入完成页的方式改为「在最后一节左滑(手势)触发」,并注明是否保留占位页。 - ---- - -## 六、风险与注意点 - -1. **多入口 path** - CompletionView 在 Growth / Profile / Discovery 三处被 present,需分别传入 `growthPath` / `profilePath` / `homePath` 的 Binding;漏传或传错会导致 `removeLast(2)` 作用在错误栈上。建议: - - 在审查/实现时逐处确认传参; - - 或为 CompletionView 封装「当前栈」来源(例如 Environment 注入当前 path),避免调用方误绑。 - -2. **NavigationPath.count** - `removeLast(2)` 前需保证 `path.count >= 2`(完成页 + 播放器)。若从深链或异常入口进入完成页,栈深度可能不足,需保留兜底(如 `dismiss()`)。 - -3. **手势与 TabView 滑动冲突** - 最后一节左滑既会触发自定义手势,也是 TabView 的翻页手势。需通过 `minimumDistance`、`coordinateSpace` 或「边缘优先」等策略区分,避免误触或重复触发。建议在真机多测:最后一节左滑、快速连续左滑、斜滑。 - -4. **从完成页返回后的 Tab 状态** - 当前实现:从完成页 dismiss 回播放器后,`onAppear` 里若 `currentNodeId == "wg://completion"` 会切回 `allCourseNodes.last?.id`。采用方案后,若使用 `removeLast(2)`,不会回到播放器,故无需再依赖该逻辑;若仍保留「先 dismiss 再回地图」的入口,需保留或等价处理该状态恢复。 - ---- - -## 七、审查结论与建议 - -| 项目 | 结论 | -|------|------| -| **架构与标准** | 方案满足「零副作用、SSOT、响应式导航、原生手感」四项标准,且比当前实现更彻底。 | -| **与现有 spec** | 与 COMPLETION_VIEW_UI_LAYER_SPEC 的「不操作 NavigationPath」冲突,需更新 spec。 | -| **实现成本** | 中等:CompletionView 接口与三处调用方改动;VerticalScreenPlayerView 增加手势与可选移除占位页;需回归测试返回与多入口。 | -| **建议** | 若采纳方案: - 1. 先更新 COMPLETION_VIEW_UI_LAYER_SPEC 与 COMPLETION_PAGE_MEANING,再改代码; - 2. CompletionView 保留 `path.count >= 2` 判断与 `dismiss()` 兜底; - 3. 手势与 TabView 的冲突在真机验证,必要时加防抖或边缘区域限制; - 4. 三处 navigationDestination 的 `Binding` 传参做清单检查,避免漏传/错传。 | - ---- - -**报告日期**:基于当前代码与所提供方案整理,未对仓库做任何代码修改。 diff --git a/ios/WildGrowth/COMPLETION_SkeletonReveal版_审查报告.md b/ios/WildGrowth/COMPLETION_SkeletonReveal版_审查报告.md deleted file mode 100644 index e0a3c27..0000000 --- a/ios/WildGrowth/COMPLETION_SkeletonReveal版_审查报告.md +++ /dev/null @@ -1,77 +0,0 @@ -# CompletionView Skeleton & Reveal 版 — 审查报告(不应用) - -**审查日期**:2025-01-29 -**范围**:Skeleton & Reveal Edition(纯白卡片 + 呼吸骨架屏 + 蓝色弥散光 + 粒子庆祝) -**结论**:仅审查、不修改仓库;需求落实正确,ParticleModifier 已按前次审查完成修复,**有一处可选视觉层级调整**。 - ---- - -## 1. 需求落实情况 - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **纯白卡片** | 彻底去「脏」,回归纯白 | `background(Color.white)` | ✅ | -| **拒绝空状态** | 未激活时用骨架屏替代留白 | IdleSkeletonView:RoundedRectangle 骨架 + 呼吸透明度 | ✅ | -| **呼吸骨架** | 暗示「数据等你揭开」 | `isBreathing ? 0.3 : 0.6` 配合 `repeatForever(autoreverses: true)` | ✅ | -| **蓝色弥散光** | 激活后四周泛蓝光 | `shadow(color: isSystemOn ? brandBlue.opacity(0.25) : ..., radius: 40)` | ✅ | -| **彩带爆炸** | 激活瞬间粒子庆祝 | ConfettiExplosion(Circle + Capsule) | ✅ | -| **布局平衡** | 激活后数字填补按钮消失空间 | ActiveDashboard 使用 Spacer 居中,无按钮占位 | ✅ | - ---- - -## 2. ParticleModifier 修复确认 - -Magic Card 版审查中的两处问题均已修正: - -| 项目 | Magic Card 版问题 | Skeleton & Reveal 实现 | 结论 | -|------|--------------------|------------------------|------| -| **弧度转换** | `cos(angle)` 使用角度制 | `angleRad = angleDegrees * .pi / 180`,使用 `cos(angleRad)` | ✅ | -| **随机值稳定** | `angle`/`distance` 为计算属性,每次重算 | `init` 中生成并存入 `let angleRad`、`let distance` | ✅ | -| **粒子颜色** | `randomElement()!` 每次重绘变化 | `confettiColors[i % confettiColors.count]` 按 index 固定 | ✅ | - ---- - -## 3. 逻辑继承 - -| 项目 | 结论 | -|------|------| -| **持久化 Key** | ✅ `has_revealed_course_\(courseId)` | -| **游客短路** | ✅ 0.5s 延迟,`performActivation(0, 0)`,不调网络 | -| **登录用户** | ✅ 0.8s 延迟 → fetchUserProfile → performActivation | -| **checkSystemStatus** | ✅ 已激活时恢复最终值 | -| **数字滚动** | ✅ `withAnimation(.linear(1.5))` + RollingNumberText | - ---- - -## 4. 可选:粒子与文字层级 - -**当前实现**:`ConfettiExplosion` 使用 `.zIndex(2)`,卡片内容 VStack 使用默认 zIndex,因此粒子叠在文字之上。 - -**注释意图**:「粒子炸在文字后面,但在卡片背景前面」——若希望粒子在文字后面,需要降低粒子的 zIndex,或提高卡片内容的 zIndex。 - -**建议**:若需粒子在文字后面,可为卡片 VStack 添加 `.zIndex(1)`,并为 `ConfettiExplosion` 使用 `.zIndex(0)` 或不设置。若希望粒子在前面以增强庆祝感,可保持现状。属视觉偏好,非必须修改。 - ---- - -## 5. 其他实现细节 - -| 项目 | 说明 | -|------|------| -| **骨架 blur(radius: 3)** | 增加模糊感,在多数设备上可接受;低端机若有卡顿再考虑去掉 | -| **呼吸动画** | `onAppear` 中 `withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true))` 驱动 `isBreathing`,逻辑正确 | -| **ScaleButtonStyle** | 未使用,无重复定义风险 | -| **接口** | 三参数不变,可直接替换 | - ---- - -## 6. 总结 - -| 维度 | 结论 | -|------|------| -| **需求落实** | ✅ 纯白、骨架屏、弥散光、粒子、布局均符合 | -| **ParticleModifier** | ✅ 弧度、随机稳定、颜色已修正 | -| **逻辑继承** | ✅ Key、游客、登录用户、数字滚动正确 | -| **粒子层级** | ⚪ 可选:按需求调整 zIndex 以实现「粒子在文字后面」 | -| **接口兼容** | ✅ 可直接替换 | - -**审查结论**:Skeleton & Reveal 版满足设计要求,ParticleModifier 已正确修复,可直接使用;如需粒子在文字后方,可按上节建议调整 zIndex。**本次未对仓库做任何修改。** diff --git a/ios/WildGrowth/COMPLETION_Y2K版_FRC_审查报告.md b/ios/WildGrowth/COMPLETION_Y2K版_FRC_审查报告.md deleted file mode 100644 index 09e2f68..0000000 --- a/ios/WildGrowth/COMPLETION_Y2K版_FRC_审查报告.md +++ /dev/null @@ -1,64 +0,0 @@ -# CompletionView Y2K 版 — 最终候选版 (FRC) 审查报告(不应用) - -**审查日期**:2025-01-29 -**范围**:Y2K Final Release Candidate(含持久化 Key 回滚 + 游客短路逻辑补全) -**结论**:仅审查、不修改仓库;两项修复已正确落实,可视为可发布候选。 - ---- - -## 1. 持久化 Key 回滚 ✅ - -| 项目 | 要求 | FRC 实现 | 结论 | -|------|------|----------|------| -| **storageKey** | 与现版一致,沿用 `has_revealed_course_\(courseId)` | `private var storageKey: String { "has_revealed_course_\(courseId)" }` | ✅ 正确 | - -从拍立得版本升级到 Y2K 后,已显影过的课程会直接显示结果,无需再次点击 SYNC。 - ---- - -## 2. 游客短路逻辑 ✅ - -| 项目 | 要求 | FRC 实现 | 结论 | -|------|------|----------|------| -| **是否调网络** | 游客不调 `fetchUserProfile` | `if userManager.isGuest { ... return }` 先判断,仅主线程延迟后 `finalizeSync()` | ✅ 不调接口 | -| **视觉延迟** | 极短“假连接”(你要求 0.5s) | `DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { finalizeSync() }`(0.6s) | ✅ 实现合理;若需严格 0.5s 可将 `0.6` 改为 `0.5` | -| **结果与持久化** | 直接成功并写 Key | `finalizeSync()` 内 `isSystemOn = true`、`UserDefaults.set(true, forKey: storageKey)`、触觉反馈 | ✅ 一致 | - -游客路径:点击 → `isBooting = true` → 0.6s 后主线程执行 `finalizeSync()` → 显影 + 写 Key + 成功反馈,无任何网络请求。 - ---- - -## 3. 登录用户路径 ✅ - -- `Task.sleep(1.2s)` → `fetchUserProfile()`(catch 忽略)→ `MainActor.run { finalizeSync() }`。 -- 与现版“拉取后显影”一致,无变更。 - ---- - -## 4. 结构与线程安全 ✅ - -| 项目 | 说明 | -|------|------| -| **finalizeSync()** | 集中处理显影 + 写 Key + `isBooting = false` + 触觉,避免重复;仅从主线程/主队列调用(`DispatchQueue.main.asyncAfter` 与 `MainActor.run`),对 `@State` 的更新安全。 | -| **SpeakerGrill(rotation:)** | 抽取为 `private func SpeakerGrill(rotation: Double) -> some View`,在 body 中调用合法,无问题。 | - ---- - -## 5. 可选小修正(非必须) - -- **延迟时长**:需求写“0.5s 假连接”,代码为 0.6s;若需严格一致,可将 `deadline: .now() + 0.6` 改为 `0.5`。 -- **ForEach**:若当前 Swift/SwiftUI 版本对 `ForEach(0..?` | 与提案一致(3 个参数,同名同类型) | ✅ 兼容 | -| **VerticalScreenPlayerView 调用** | `CompletionView(courseId:, courseTitle:, navigationPath:)` | 无需修改 | ✅ 可直接编译 | -| **GrowthView / 其他 navigationDestination** | 若存在 `.completion` 且传上述 3 参数 | 无需修改 | ✅ 无影响 | - -替换为 Y2K 版后,仅需整文件替换 `CompletionView.swift`,**无需改任何调用处**。 - ---- - -## 2. 数据与依赖一致性 - -| 项目 | 当前实现 | Y2K 提案 | 结论 | -|------|----------|----------|------| -| **UserManager** | `UserManager.shared`,`studyStats.(time, lessons)` | 相同 | ✅ | -| **导航** | `navStore.switchToGrowthTab()` + `navigationPath?.wrappedValue = NavigationPath()` 或 `dismiss()` | 与提案一致 | ✅ | -| **持久化 key** | `has_revealed_course_\(courseId)` | `has_y2k_booted_\(courseId)` | ⚠️ 见下 | - -- **持久化 key 不同**:若从当前「赛博拍立得」版直接切到 Y2K 版,用户在本课程下会视为「未启动过」Y2K,需再按一次 SYNC 才会显示结果。若希望换肤后「已显影过的课程仍为已显影」,可把 Y2K 的 `storageKey` 改为与现版一致(例如继续用 `has_revealed_course_\(courseId)`),否则保留 `has_y2k_booted_\(courseId)` 亦可,属产品选择。 - ---- - -## 3. 逻辑差异(游客与加载) - -| 项目 | 当前实现 | Y2K 提案 | 建议 | -|------|----------|----------|------| -| **游客 (isGuest)** | 点击后不调 API,直接显影并写持久化 | 仍执行 `Task.sleep` + `fetchUserProfile()`,catch 后置 `isSystemOn = true` | 建议在 Y2K 的 `startUpload()` 内保留与现版一致的游客分支:若 `userManager.isGuest` 则直接设 `isSystemOn = true` 并写持久化,不调 `fetchUserProfile`,避免无谓请求与约 1.2s 延迟。 | -| **登录用户** | 拉取 `fetchUserProfile` 后显影 | 同(1.2s 后拉取 + 显影) | ✅ 行为一致 | - -若严格遵循「只换皮肤、逻辑不变」,建议在 Y2K 版中补上与现版相同的 `isGuest` 短路逻辑。 - ---- - -## 4. UI / 实现细节核对 - -- **Y2K 外壳与屏幕**:半透明渐变 + `strokeBorder` 高光 + `.ultraThinMaterial`、LCD 开/关色、果冻按钮与 SYNC DATA/进度态,均为纯视觉,无逻辑影响。 -- **Y2KIdleView / Y2KResultView**:使用 `userManager.studyStats.lessons` / `time` 与当前数据源一致;字体与 XP 风格进度条仅为样式。 -- **ForEach(0..` 配合 `id: \.self` 在 SwiftUI 中合法,无需改。 -- **屏幕内布局**:内屏 `overlay` 中再 `.padding(12)` 会形成双层内边距;若希望与现版内边距一致,可后续微调数值,非阻塞。 - ---- - -## 5. 影响范围(仅换 CompletionView 时) - -- **仅替换** `Views/CompletionView.swift` 为 Y2K 版时: - - **VerticalScreenPlayerView**:无需改动。 - - **MapView / MainTabView / GrowthView / ProfileView / DiscoveryView**:无改动、无影响。 -- 若采纳「持久化 key 统一」或「游客短路」建议,仅需在 Y2K 版单文件内修改,不涉及其他页面。 - ---- - -## 6. 总结 - -| 维度 | 结论 | -|------|------| -| **接口兼容** | ✅ 三参数一致,调用方无需修改 | -| **数据与导航** | ✅ 与现版一致 | -| **持久化 key** | ⚠️ 与现版不同,换肤后需再按一次 SYNC(可按需统一 key) | -| **游客逻辑** | ⚠️ 建议在 Y2K 版中保留与现版相同的 isGuest 短路,实现「只换皮肤」 | -| **其他页面** | ✅ 无影响 | - -**审查结论**:Y2K 版可作为「仅换皮肤」的替换方案;建议在应用前(若采纳)补上游客短路逻辑,并按产品需求决定是否统一持久化 key。**本次未对仓库做任何修改。** diff --git a/ios/WildGrowth/COMPLETION_git_1.30dazhi合并前_变更清单.md b/ios/WildGrowth/COMPLETION_git_1.30dazhi合并前_变更清单.md deleted file mode 100644 index a556214..0000000 --- a/ios/WildGrowth/COMPLETION_git_1.30dazhi合并前_变更清单.md +++ /dev/null @@ -1,136 +0,0 @@ -# 1.30dazhi合并前 · 完结页相关变更清单(禁止应用) - -**说明**:根据 git 拉取的 **1.30dazhi合并前** 分支与 commit **f725f31** 核对:除完结页外,**其他页面是否有被影响**。 -**结论**:仅做审查与清单整理,**绝对禁止应用任何代码**。 - ---- - -## 一、当前分支与提交 - -- **当前分支**:`1.30dazhi合并前` -- **完结页相关提交**:`f725f31` — `feat: 完成页逻辑与占位页进入、无完成按钮;课程完成导航与文档` -- **工作区**:有 **1 个未提交修改** — `ios/WildGrowth/WildGrowth/VerticalScreenPlayerView.swift`(约 41 行变更) - ---- - -## 二、f725f31 中「除完结页外」被改动的文件(其他页面) - -在 **f725f31** 里,**除了** 新增的 `CompletionView.swift` 和文档、以及 `VerticalScreenPlayerView.swift` 的完成页逻辑外,**以下页面/文件也被一起改动了**: - -### 1. CourseNavigation.swift - -| 变更 | 说明 | -|------|------| -| `.player` | 由 `(courseId, nodeId)` 改为 `(courseId, nodeId, isLastNode, courseTitle)` | -| 新增 `.completion` | `(courseId, courseTitle, completedLessonCount)` | -| 注释 | 「从 HomeView 提取」改为「供 DiscoveryView / GrowthView / MapView / ProfileView 共用」 | - -**影响**:所有使用 `CourseNavigation.player` / `.completion` 的调用方必须传新参数或处理新 case。 - ---- - -### 2. MainTabView.swift - -| 变更 | 说明 | -|------|------| -| Tab 选中状态 | 由 `@State private var selection` 改为使用 `navStore.selectedTab` | -| 新增 `NavigationStore` | `@Published var selectedTab`、`func switchToGrowthTab()` | -| TabView 绑定 | `TabView(selection: $selection)` → `TabView(selection: $navStore.selectedTab)` | -| 登录成功跳转 | `selection = 2` → `navStore.selectedTab = 2` | -| 多处 print | `selection` → `navStore.selectedTab` | - -**影响**:Tab 切换与完成页「回到技能页」依赖 `navStore.selectedTab` 与 `switchToGrowthTab()`。 - ---- - -### 3. MapView.swift - -| 变更 | 说明 | -|------|------| -| 点击小节进入播放器 | `CourseNavigation.player(courseId, nodeId)` → `CourseNavigation.player(courseId, nodeId, isLastNode, courseTitle)` | -| 新增 `isLastNode` | `(chapter.id == data.chapters.last?.id) && (index == chapter.nodes.count - 1)` | -| 传入 `courseTitle` | `data.courseTitle` | - -**影响**:从地图进播放器时,会多传 `isLastNode`、`courseTitle`,与 `CourseNavigation` 新签名一致。 - ---- - -### 4. ProfileView.swift - -| 变更 | 说明 | -|------|------| -| `.map` | `MapView(courseId:)` → `MapView(courseId:, navigationPath: $navStore.profilePath)` | -| `.player` | `VerticalScreenPlayerView` 增加 `navigationPath: $navStore.profilePath`、`isLastNode`、`courseTitle` | -| 新增 `.completion` | `CompletionView(courseId, courseTitle, completedLessonCount)` | - -**影响**:我的 Tab 下地图、播放器、完成页的导航与传参全部按新枚举和完成页流程改过。 - ---- - -### 5. DiscoveryView.swift - -| 变更 | 说明 | -|------|------| -| `.map` | `MapView(courseId:)` → `MapView(courseId:, navigationPath: $navStore.homePath)` | -| `.player` | `VerticalScreenPlayerView` 增加 `isLastNode`、`courseTitle`(原已有 navigationPath) | -| 新增 `.completion` | `CompletionView(courseId, courseTitle, completedLessonCount)` | - -**影响**:发现 Tab 下地图、播放器、完成页的导航与传参全部按新枚举和完成页流程改过。 - ---- - -### 6. GrowthView.swift - -| 变更 | 说明 | -|------|------| -| `.player` | `VerticalScreenPlayerView` 增加 `isLastNode`、`courseTitle`(原已有 MapView navigationPath) | -| 新增 `.completion` | `CompletionView(courseId, courseTitle, completedLessonCount)` | - -**影响**:技能 Tab 下播放器、完成页的导航与传参全部按新枚举和完成页流程改过。 - ---- - -### 7. VerticalScreenPlayerView.swift(f725f31 内) - -| 变更 | 说明 | -|------|------| -| 占位页 | `CompletionPlaceholderPage` 由「带 courseId/courseTitle/navigationPath + onAppear 里 push」改为「纯视觉占位」 | -| 新增 | TabView 上 `.onChange(of: currentNodeId)`,当 `newId == "wg://completion"` 时 0.1s 后 append `.completion` | - -**影响**:完成页进入方式从「占位页 onAppear」改为「父视图 onChange」,避免预加载误触。 - ---- - -## 三、当前工作区未提交改动(仅 1 个文件) - -| 文件 | 变更概要 | -|------|----------| -| **VerticalScreenPlayerView.swift** | 在 f725f31 基础上:CompletionPlaceholderPage 去掉参数、改为纯 ZStack;TabView 增加 `.onChange(of: currentNodeId)` 的完成页 push 逻辑(与上面「f725f31 内」描述一致,可能是同一逻辑的又一次提交前微调或重复修改) | - -**其他页面**:当前 **无** 未提交修改。GrowthView、ProfileView、DiscoveryView、MapView、MainTabView、CourseNavigation 等在工作区均为已提交状态(f725f31)。 - ---- - -## 四、直接回答「除了完结页,其他页面有没有被影响」 - -**有。** - -在引入完结页的 **f725f31** 中,除了: - -- 新增:`CompletionView.swift`、`COMPLETION_PAGE_MEANING.md`、`COMPLETION_VIEW_*` 等文档 -- 修改:`VerticalScreenPlayerView.swift`(占位页 + 完成页进入逻辑) - -还**一并修改了**以下「其他页面」以支持完成页导航与播放器参数: - -1. **CourseNavigation.swift** — 枚举增加 `.completion`、`.player` 增加 `isLastNode`、`courseTitle` -2. **MainTabView.swift** — Tab 状态迁到 `navStore.selectedTab`,新增 `switchToGrowthTab()` -3. **MapView.swift** — 进播放器时传 `isLastNode`、`courseTitle` -4. **ProfileView.swift** — MapView 传 navigationPath;播放器传 isLastNode、courseTitle;增加 `.completion` destination -5. **DiscoveryView.swift** — MapView 传 navigationPath;播放器传 isLastNode、courseTitle;增加 `.completion` destination -6. **GrowthView.swift** — 播放器传 isLastNode、courseTitle;增加 `.completion` destination - -因此:**1.30dazhi合并前** 分支上,完结页不是「只动完结页」,而是**和上述 6 个文件一起**在 f725f31 里改的;当前工作区里,只有 **VerticalScreenPlayerView.swift** 还有未提交修改,其他页面没有新的未提交变更。 - ---- - -**未对仓库进行任何应用或修改操作。** diff --git a/ios/WildGrowth/COMPLETION_iOS原生风格_定稿审查报告.md b/ios/WildGrowth/COMPLETION_iOS原生风格_定稿审查报告.md deleted file mode 100644 index 9bc224f..0000000 --- a/ios/WildGrowth/COMPLETION_iOS原生风格_定稿审查报告.md +++ /dev/null @@ -1,107 +0,0 @@ -# CompletionView iOS 原生风格版 — 定稿审查报告(不应用) - -**审查日期**:2025-01-29 -**范围**:Final iOS Native Style(systemGroupedBackground + 白卡片 + System Blue + Widget 布局) -**结论**:仅审查、不修改仓库;需求落实正确,**有一处必须修复**(ScaleButtonStyle 重复定义)。 - ---- - -## 1. 需求落实情况 - -### 视觉风格 - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **背景** | systemGroupedBackground 浅灰 | `Color(UIColor.systemGroupedBackground)` | ✅ | -| **卡片** | 纯白圆角 + 柔和投影 | `cardBg = .white`,`.cornerRadius(16)`,`shadow(radius:15, y:6)` | ✅ | -| **配色** | System Blue(品牌蓝) | `brandBlue = Color.blue` | ✅ | -| **布局** | Widget 小组件式 | 卡片头(学习统计 + chart 图标)+ 主视觉(小节数)+ 次视觉(专注时长) | ✅ | -| **主/次视觉** | 超大小节数 + 底部专注时长 | 80pt 数字 + 底部一行「专注时长 X min」 | ✅ | - -### 交互与动效 - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **按钮文案** | 上传数据 → Loading → 已同步 | 三态:`Text("上传数据")` / `ProgressView` / `HStack(checkmark + "已同步")` | ✅ | -| **按钮颜色** | 严禁变绿,始终蓝底 | `background(brandBlue)` | ✅ | -| **屏幕动效** | 数据加载完成,内容淡入 | `withAnimation(.easeOut(0.3)) { isSystemOn = true }` | ✅ | -| **数字动效** | 从 0 匀速滚动到目标,1.5s | `withAnimation(.linear(1.5)) { displayLessons/Minutes }` + `RollingNumberText` | ✅ | - -### 业务逻辑 - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **数据源** | UserManager.shared | `@ObservedObject userManager = UserManager.shared` | ✅ | -| **持久化** | UserDefaults 防重复动效 | `storageKey: "has_revealed_course_\(courseId)"` | ✅ | -| **导航** | `navStore.switchToGrowthTab()` | 一致 | ✅ | - ---- - -## 2. 逻辑继承(FRC 要求) - -| 项目 | 结论 | -|------|------| -| **持久化 Key** | ✅ `has_revealed_course_\(courseId)` | -| **游客短路** | ✅ 0.5s 延迟,`performActivation(0, 0)`,不调网络 | -| **登录用户** | ✅ 0.8s 延迟 → fetchUserProfile → performActivation | -| **checkSystemStatus** | ✅ 已激活时恢复最终值,无动画 | - ---- - -## 3. ⚠️ 必须修复:ScaleButtonStyle 重复定义 - -**问题**:定稿代码在文件末尾定义了 `struct ScaleButtonStyle: ButtonStyle`,而项目中已在 `NotebookListView.swift`(约第 287 行)定义同名 struct。同一 target 内存在两个 `ScaleButtonStyle` 会导致 **invalid redeclaration** 编译错误。 - -**修复**:删除 CompletionView.swift 中 `ScaleButtonStyle` 的完整定义(`struct ScaleButtonStyle: ButtonStyle { ... }`),直接使用项目已有的实现。`ScaleButtonStyle` 在同一模块内可访问,无需额外导入。 - ---- - -## 4. RollingNumberText - -- `RollingNumberText` 仅在 CompletionView 相关代码中使用,当前实现 `Animatable` 正确,与定稿逻辑一致。 -- 保留在 CompletionView.swift 内不会产生符号冲突。 - ---- - -## 5. 模块化与 HIG 符合度 - -- 将 `SummaryCardView`、`ActiveDashboardView`、`IdlePlaceholderView`、`UploadButton` 拆分为 computed property,结构清晰。 -- 使用 `Color(UIColor.systemGroupedBackground)`、`Color(UIColor.secondarySystemGroupedBackground)` 等系统颜色,符合 iOS HIG。 -- `ScaleButtonStyle` 的按压缩放效果与 NotebookListView 中的实现一致,视觉体验统一。 - ---- - -## 6. 接口与影响范围 - -| 项目 | 结论 | -|------|------| -| **初始化参数** | 三参数不变 | -| **替换方式** | 整文件替换 `CompletionView.swift` | -| **其他页面** | 无影响 | - ---- - -## 7. 总结 - -| 维度 | 结论 | -|------|------| -| **需求落实** | ✅ 视觉、交互、动效、业务逻辑均符合要求 | -| **逻辑继承** | ✅ Key、游客、登录用户逻辑正确 | -| **ScaleButtonStyle** | ❌ **必须删除定稿中的定义**,改用项目已有实现 | -| **接口兼容** | ✅ 可直接替换 | - -**审查结论**:定稿在逻辑和需求上正确,应用前需**删除 CompletionView.swift 中的 `struct ScaleButtonStyle` 定义**,否则无法通过编译。**本次未对仓库做任何修改。** - ---- - -## 8. 修正版审查确认(2025-01-29 续) - -**修正内容**:已移除 `ScaleButtonStyle` 结构体定义,仅保留 `.buttonStyle(ScaleButtonStyle())` 调用,复用 `NotebookListView.swift` 中已有实现。 - -| 项目 | 修正前 | 修正后 | 结论 | -|------|--------|--------|------| -| **ScaleButtonStyle** | 文件末尾重复定义 | 已删除,使用项目已有 | ✅ 修复完成 | -| **调用处** | `.buttonStyle(ScaleButtonStyle())` | 不变,解析为 NotebookListView 中定义 | ✅ 正确 | -| **注释** | — | 添加「使用项目已有」「此处不重复声明」 | ✅ 便于后续维护 | - -**结论**:修正版可**直接替换** `CompletionView.swift`,无编译冲突。**本次未对仓库做任何修改。** diff --git a/ios/WildGrowth/COMPLETION_实现后行为与影响说明.md b/ios/WildGrowth/COMPLETION_实现后行为与影响说明.md deleted file mode 100644 index 684f40b..0000000 --- a/ios/WildGrowth/COMPLETION_实现后行为与影响说明.md +++ /dev/null @@ -1,129 +0,0 @@ -# 完成页导航方案 — 实现后行为与影响说明 - -说明:实现方案后**会变成什么样**、**会不会影响原有逻辑和展示**、**会不会导致其他地方出问题**。不修改代码,仅作说明。 - ---- - -## 一、实现后会变成什么样 - -### 1. 进入完成页(两种方式,二选一或并存) - -| 方式 | 当前 | 实现后(若保留占位页 + 加手势) | -|------|------|----------------------------------| -| **左滑到「下一页」** | 最后一节再左滑 → 进入占位页(空白)→ 0.1s 后 push 完成页 | 不变:仍可滑到占位页,再由 `onChange` push | -| **在最后一节左滑** | 无 | 新增:在最后一节直接左滑(手势识别)→ 可设为「切到占位页再 push」或「直接 push 完成页」 | - -若方案采用「删除占位页、仅手势」: -- 最后一节左滑 → 直接 push 完成页,**不再出现空白占位页那一屏**。 -- TabView 只有真实课程页,不再有「多一页」的占位。 - -**展示上**:完成页本身 UI 不变(仍是当前 CompletionView 的 3D 卡片、打字机等);变的只是「怎么进」和「怎么回」。 - ---- - -### 2. 从完成页返回(核心变化) - -| 操作 | 当前 | 实现后 | -|------|------|--------| -| **顶部返回 (chevron)** | `dismiss()` → 回到播放器(最后一节或占位页) | 可保持不变:仍 `dismiss()`,回到播放器 | -| **底部「继续学习」** | `navStore.switchToGrowthTab()` + `dismiss()` | `handleReturnToMap()`:`navigationPath.removeLast(2)` | - -**「继续学习」行为对比**: - -- **当前**: - 1. `dismiss()` → 栈 pop 一层,回到 **VerticalScreenPlayerView**(当前 Tab 可能是占位页,`onAppear` 里会切回最后一节)。 - 2. `switchToGrowthTab()` → 切到「技能」Tab,且 **清空 `growthPath`**(`growthPath = NavigationPath()`)。 - 3. 用户最终看到的是 **技能 Tab 的根界面(课程列表)**,不是地图。 - -- **实现后**: - 1. `removeLast(2)` → 一次 pop 掉「完成页」和「播放器」。 - 2. 栈变成:**[MapView]**(或更短,视当前 path 而定)。 - 3. **不**调用 `switchToGrowthTab()`,所以**不会清空 path**,也**不一定会切 Tab**。 - 4. 用户最终看到的是 **当前 Tab 下的地图页**(从哪个 Tab 进的,就还在哪个 Tab)。 - -**总结**: -- 实现后,点「继续学习」会**直接回到地图**,且**保留在当前 Tab**(发现 / 技能 / 我的)。 -- 当前是**回到课程列表**(且强制在技能 Tab)。这是**产品行为上的明确变化**。 - ---- - -### 3. 三条入口分别会怎样 - -| 入口 | 当前栈(到完成页时) | 当前「继续学习」后 | 实现后「继续学习」后 | -|------|----------------------|--------------------|------------------------| -| **技能 Tab** | growthPath: [Map, Player, Completion] | 切到技能 Tab + 清空栈 → **课程列表** | 仍技能 Tab,栈 [Map] → **地图** | -| **发现 Tab** | homePath: [Map, Player, Completion] | dismiss 回播放器;switchToGrowthTab 切到技能并清空 → **课程列表** | 仍发现 Tab,栈 [Map] → **地图** | -| **我的 Tab** | profilePath: [Map, Player, Completion] | 同上 → **课程列表** | 仍我的 Tab,栈 [Map] → **地图** | - ---- - -## 二、会不会影响原有逻辑和展示 - -### 1. 会改变的部分(有意为之) - -- **底部按钮语义**:「继续学习」从「回课程列表(并切到技能)」变为「回地图(且留在当前 Tab)」。 -- **是否清空栈**:不再在完成页里清空 `growthPath`,所以不会出现「从完成页一点就回到空白课程列表」。 -- **是否切 Tab**:不再强制切到技能 Tab;用户会留在发现 / 技能 / 我的中的当前 Tab。 - -若产品期望就是「完成课后回到地图、留在当前 Tab」,则与方案一致;若期望是「回到课程列表、且一定在技能 Tab」,则与**当前**一致,与**实现后**不一致,需要产品确认。 - -### 2. 可保持不变的部分 - -- **完成页 UI**:3D 翻转卡片、打字机、顶部返回、按钮样式等都可以不改。 -- **顶部返回**:继续用 `dismiss()`,仍回到播放器,逻辑和展示都不变。 -- **进入完成页的方式**:若保留占位页,现有「滑到占位页再 push」仍可用;只是多了一种「最后一节左滑」的触发方式(若加手势)。 - -### 3. 依赖「当前行为」的地方 - -- **NavigationStore.switchToGrowthTab()**:当前只在 CompletionView 的「继续学习」里被调用。实现后 CompletionView 不再调用它,**不会影响其他使用处**(因为别处没有用这个方法)。 -- **growthPath 被清空**:当前只有「完成页点继续学习」会清空 growthPath。若没有「从完成页返回后依赖 growthPath 为空」的逻辑,则实现后**不会破坏其它功能**。 - ---- - -## 三、会不会导致其他地方出问题 - -### 1. 三个 Tab 的 navigationDestination(必须改) - -- **GrowthView / ProfileView / DiscoveryView** 里 `.completion` 分支都要给 CompletionView 传 **Binding**: - `navigationPath: $navStore.growthPath` / `$navStore.profilePath` / `$navStore.homePath`。 -- **漏传或错传**: - - 漏传 → 编译不通过(CompletionView 多了必选参数)。 - - 错传(例如发现进的完成页却传了 `growthPath`)→ 点「继续学习」会 pop 错栈,可能白屏或回到错误 Tab。 -- **结论**:三处都必须改,且必须传**当前栈对应的 path**,否则会出问题。 - -### 2. 笔记流(NoteTreeView / NoteListView)— 不受影响 - -- 笔记流用的是 **NoteNavigationDestination.player**,打开的是 **VerticalScreenPlayerView**,且**没有传 navigationPath**(或传的是笔记自己的 path)。 -- 在这些地方打开的播放器里,`navigationPath` 为 nil,现有逻辑里 `onChange` 里会 `guard let path = navigationPath else { return }`,**不会 push CourseNavigation.completion**。 -- 因此:从笔记进的播放器,**不会**出现「滑到完成页」的 push(除非你后续在笔记流里也接 Course 的 completion)。 -- **结论**:实现完成页方案**不会影响笔记流**,也不会从笔记流误进完成页。 - -### 3. 从完成页返回时栈深度不足 - -- 正常流程栈至少为 [Map, Player, Completion],`removeLast(2)` 安全。 -- 若将来有「深链、通知、分享」等直接打开 CompletionView,栈可能只有 [Completion],此时 `path.count >= 2` 为假,应走兜底 `dismiss()`。 -- 方案里已建议保留 `if navigationPath.count >= 2 { removeLast(2) } else { dismiss() }`,**不会因为栈浅而崩溃**,最多退回单层 pop。 - -### 4. 手势与 TabView 滑动 - -- 在最后一节加「左滑进完成页」时,与 TabView 自带的左滑翻页**共用同一方向**,可能冲突(例如:想翻页却触发了完成页,或想进完成页却只翻到占位页)。 -- 若用「边缘左滑」或阈值(如 -60pt)、防抖,可减轻误触;需在真机多测。 -- **结论**:可能带来**体验上的小问题**(误触/难触),不是逻辑错误,可通过手势参数和测试收敛。 - -### 5. 其他使用 VerticalScreenPlayerView / CompletionView 的地方 - -- **VerticalScreenPlayerView**:除 GrowthView / ProfileView / DiscoveryView 外,仅在 **NoteTreeView / NoteListView** 出现,且走的是 NoteNavigationDestination,不涉及 CourseNavigation.completion,**不受影响**。 -- **CompletionView**:只在这三个 Tab 的 `navigationDestination(for: CourseNavigation.self)` 的 `.completion` 分支出现,**没有其它调用点**。 -- **结论**:只要三处传参正确,**不会导致其它页面逻辑错误**。 - ---- - -## 四、简要结论表 - -| 问题 | 结论 | -|------|------| -| 实现后会变成什么样? | 进入完成页可增加「最后一节左滑」触发;「继续学习」从「回课程列表 + 切技能 Tab」变为「直接回地图 + 保留当前 Tab」;可选去掉占位页。 | -| 会不会影响原有逻辑和展示? | 会:底部按钮语义和最终停留页(地图 vs 课程列表)、是否清栈/切 Tab 会变;顶部返回和完成页 UI 可保持不变。 | -| 会不会导致其他地方出问题? | 三处传 Binding 必须正确,否则会 pop 错栈;笔记流、其它使用点不受影响;栈浅时用 dismiss 兜底;手势可能与 TabView 滑动冲突,需真机调参。 | - -**建议**:实现前与产品确认「继续学习」的预期是「回地图」还是「回课程列表」;若确认为回地图且保留当前 Tab,再按方案改并保证三处 path 传参一致。 diff --git a/ios/WildGrowth/COMPLETION_强力侧滑版_审查报告.md b/ios/WildGrowth/COMPLETION_强力侧滑版_审查报告.md deleted file mode 100644 index 5eb12e0..0000000 --- a/ios/WildGrowth/COMPLETION_强力侧滑版_审查报告.md +++ /dev/null @@ -1,82 +0,0 @@ -# 完成页「强力侧滑版」— 审查报告(禁止应用) - -**审查对象**:粉紫勋章视觉 + RobustSwipeBackEnabler(didMove + viewDidAppear 双节点强制开启侧滑) -**结论**:仅审查,不修改仓库内任何文件。 - ---- - -## 一、问题与方案对应 - -| 问题 | 方案 | 审查结论 | -|------|------|----------| -| 隐藏导航栏后侧滑返回失效 | 用 `UIViewController` 子类在 `didMove(toParent:)` 与 `viewDidAppear` 两处调用 `enableGesture()` | ✅ 思路正确,双节点可提高找回手势的概率 | -| 之前 SwipeBackEnabler 时机不对或被覆盖 | 使用自定义 `SwipeBackController` 继承 `UIViewController`,在生命周期中多次执行 | ✅ 比仅用 `updateUIViewController` 的 Representable 更稳 | - ---- - -## 二、强力侧滑实现审查 - -### 2.1 RobustSwipeBackEnabler + SwipeBackController - -- **Representable**:`makeUIViewController` 返回 `SwipeBackController()`,`updateUIViewController` 为空,符合「只挂一个控制器做生命周期」的用法。 -- **SwipeBackController**: - - `didMove(toParent:)`:视图被加入/移出父控制器时调用,此时通常已进入导航栈,可拿到 `navigationController`。 - - `viewDidAppear`:每次该页显示时再执行一次,可应对 SwiftUI 更新或系统把手势关掉的情况。 -- **enableGesture()**: - `nc.interactivePopGestureRecognizer?.delegate = nil` + `isEnabled = true` 是常见做法,用于在隐藏导航栏时恢复全屏侧滑。逻辑正确。 - -### 2.2 潜在注意点(非否决项) - -1. **视图层级**:`.background(RobustSwipeBackEnabler())` 会把 `SwipeBackController` 的 view 作为 CompletionView 的底层。SwiftUI 的 NavigationStack 对应底层会有一个 `UINavigationController`,`self.navigationController` 应是该栈,因此一般能正确找到。若将来把 CompletionView 放在非 NavigationStack 的容器里,需再确认 `navigationController` 是否仍为预期栈。 -2. **多次执行**:`didMove` / `viewDidAppear` 可能被多次调用(例如 SwiftUI 重绘、present 方式变化),重复执行 `enableGesture()` 无副作用,可接受。 -3. **真机验证**:建议在真机上从「发现 / 技能 / 我的」三个 Tab 分别进入完成页,各做几次右滑返回,确认无偶发失效;若仍有失效,可再在 `viewWillAppear` 或短延迟 `DispatchQueue.main.async` 中补一次 `enableGesture()`。 - ---- - -## 三、视觉与交互审查 - -### 3.1 与需求对照 - -| 需求 | 实现 | 结论 | -|------|------|------| -| 粉紫勋章:点前灰色+「完成」,点后粉紫+「共完成 XX 节」 | 未激活:灰底 + 灰边 + 「完成」;激活:粉紫渐变圆 + 白字「共完成 / N 小节」+ 冲击波 | ✅ 一致 | -| 顶部无返回按钮,只有标题「已完成」 | `ZStack` 中仅 `Text("已完成")`,无 Button | ✅ 一致 | -| 底部淡蓝按钮、无箭头 | `Text("回到我的内容")`,`.foregroundColor(.brandVital)`,`Capsule().fill(Color.brandVital.opacity(0.08))`,无 SF Symbol | ✅ 一致 | -| 右滑回最后一节 | 依赖 RobustSwipeBackEnabler | ✅ 见上 | -| 底部按钮回技能页根 | `handleReturnToRoot()` → `navStore.switchToGrowthTab()` | ✅ 一致 | - -### 3.2 配色与 DesignSystem - -- 方案中本地定义: - - `cyberPink = Color(red: 1.0, green: 0.25, blue: 0.50)` → 等价 `#FF4081` - - `cyberPurple = Color(red: 0.54, green: 0.31, blue: 1.0)` → 等价 `#8A4FFF` -- **DesignSystem** 中已有: - - `Color.cyberNeon` = `#FF4081` - - `Color.cyberIris` = `#8A4FFF` -- **建议**(可选):将勋章渐变的 `cyberPink` / `cyberPurple` 改为 `Color.cyberNeon` / `Color.cyberIris`,避免重复定义并统一设计系统;若你希望完成页与 DesignSystem 解耦则可保留当前写法。 - -### 3.3 其他细节 - -- 冲击波:`rippleScale` 从 1.0 到 1.8、`rippleOpacity` 从 1.0 到 0,时长 0.8s,视觉合理。 -- 震动:`UIImpactFeedbackGenerator(style: .heavy)`,与「点亮」动作匹配。 -- 底部按钮常驻显示(不依赖 `isActive`),与「回到我的内容」随时可点一致。 - ---- - -## 四、接口与调用方 - -- **CompletionView** 仍为三参:`courseId`, `courseTitle`, `completedLessonCount`,依赖 `@EnvironmentObject navStore`。 -- **GrowthView / ProfileView / DiscoveryView** 无需改构造或传参。 - ---- - -## 五、审查结论汇总 - -| 项 | 结论 | -|----|------| -| 强力侧滑方案 | 双生命周期节点(didMove + viewDidAppear)合理,实现正确;建议真机多入口验证,必要时可再加 viewWillAppear 或短延迟兜底。 | -| 粉紫勋章视觉 | 与描述一致;可选改用 DesignSystem 的 cyberNeon/cyberIris 统一色值。 | -| 顶部 / 底部 / 交互 | 符合「无返回、仅标题、淡蓝按钮无箭头、底部回技能页根」的要求。 | -| 全量替换范围 | 仅替换 `CompletionView.swift` 即可;VerticalScreenPlayerView 等不动。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_新中式赛博版_审查报告.md b/ios/WildGrowth/COMPLETION_新中式赛博版_审查报告.md deleted file mode 100644 index 9e95097..0000000 --- a/ios/WildGrowth/COMPLETION_新中式赛博版_审查报告.md +++ /dev/null @@ -1,100 +0,0 @@ -# CompletionView「新中式赛博」版设计方案 — 审查报告(不应用) - -## 一、结论摘要 - -- **接口**:与现有调用方完全兼容,无需修改 `VerticalScreenPlayerView` 等。 -- **逻辑**:导航与“回到我的内容”行为与现有一致;仅交互由「点击勋章激活」改为「点击卡片 3D 翻转」。 -- **建议**:设计说明中的「微小的上箭头」与代码中的 `sparkles` 不一致,若需严格按文案可改为 `arrow.up`;其余可合并。 - ---- - -## 二、接口与调用方 - -| 项目 | 当前 CompletionView | 提案代码 | 结论 | -|------|---------------------|----------|------| -| 入参 | `courseId`, `courseTitle?`, `completedLessonCount`, `navigationPath?` | 同左 | ✅ 一致 | -| 调用处 | `VerticalScreenPlayerView` 传入上述 4 项 | 无需改动 | ✅ 兼容 | - -调用处代码保持不变即可: - -```swift -CompletionView( - courseId: courseId, - courseTitle: self.courseTitle ?? mapData?.courseTitle, - completedLessonCount: UserManager.shared.studyStats.lessons, - navigationPath: navigationPath -) -``` - ---- - -## 三、行为与逻辑 - -| 能力 | 当前实现 | 提案实现 | 结论 | -|------|----------|----------|------| -| 清空栈 / 返回 | `handlePopToRoot()`:有 `navigationPath` 则 `path.wrappedValue = NavigationPath()`,否则 `dismiss()` | 相同 | ✅ 一致 | -| 触觉反馈 | 点击时 heavy,按钮时 light | 翻转时 medium,按钮时 light | ✅ 可接受 | -| 隐藏导航栏 | `.toolbar(.hidden, for: .navigationBar)` | 同左 | ✅ 一致 | - -交互变化仅为表现形式: - -- **当前**:点击圆形勋章 → 激活态 → 显示「共完成 X 小节」+ 底部「回到我的内容」。 -- **提案**:点击卡片 → 3D 翻转 → 背面「共完成 X 小节」+ 底部「回到我的内容」(翻转后按钮由 0.6 到 1 透明度)。 - -语义一致,无逻辑冲突。 - ---- - -## 四、视觉与实现细节 - -1. **背景** - 提案使用 `Color.bgPaper`,当前为 `Color.white`。与 app 内其他页面统一,更一致,✅ 合理。 - -2. **正面 (CardFrontView)** - - 竖排「完」「成」、180pt、serif、渐变 + 0.8 透明度、微旋转、裁切与 `drawingGroup` 均合理。 - - 底部提示:「点击开启」+ 图标。设计说明为「微小的上箭头」,代码为 `Image(systemName: "sparkles")`。若需与说明严格一致,建议改为 `arrow.up` 或同时保留「点击开启」文案。 - -3. **背面 (CardBackView)** - - 「共完成」+ 数字 +「小节」、渐变底、装饰圆环,与当前信息一致,✅。 - -4. **底部按钮** - - 文案「回到我的内容」+ 箭头不变。 - - 提案为白底胶囊 + 淡描边,未翻转时 `opacity(0.6)`,翻转后 1;当前为渐变按钮始终可见。属设计取舍,✅ 可接受。 - -5. **渐变色** - - 提案背面紫为 `Color(red: 0.58, green: 0.28, blue: 0.95)`,当前为 `0.62, 0.35, 1.0`,差异较小,✅ 无问题。 - ---- - -## 五、潜在风险与注意点 - -| 项 | 说明 | 风险 | -|----|------|------| -| `.foregroundStyle(gradient.opacity(0.8))` | `LinearGradient` 的 `.opacity(0.8)` 在 iOS 15+ 作为 `ShapeStyle` 使用无问题 | 低 | -| `rotation3DEffect` + `perspective: 0.8` | 常规用法 | 无 | -| CardFrontView 呼吸动画 | `hintOpacity` 0.3 ↔ 0.8,`repeatForever(autoreverses: true)` | 无 | -| 卡片尺寸 280×400 | 与当前 200×200 圆不同,仅布局变化 | 无 | - -未发现编译或运行时错误;替换为提案代码后,仅需在真机/模拟器上确认一次翻转与返回流程即可。 - ---- - -## 六、与设计说明的对照 - -| 设计说明 | 代码实现 | 一致性 | -|----------|----------|--------| -| 竖排超大字「完」「成」 | VStack + 180pt serif | ✅ | -| 出血/裁切感 | offset + clipShape + 圆角 | ✅ | -| 赛博粉紫渐变、降饱和度 | gradient.opacity(0.8) | ✅ | -| 底部「点击开启」 | Text("点击开启") | ✅ | -| 「微小的上箭头」 | 使用 `sparkles` 图标 | ⚠️ 不一致,可改为 `arrow.up` | -| 去掉圆圈 | 无圆圈,改为矩形卡片 | ✅ | -| 宋体 (Serif) | `.design(.serif)` | ✅ | - ---- - -## 七、审查结论 - -- **可合并**:接口、导航与核心逻辑与现有一致,无破坏性变更。 -- **可选修改**:将底部提示图标由 `sparkles` 改为 `arrow.up`,与「微小的上箭头」说明统一。 -- **不应用**:按你要求本次仅审查,不替换 `CompletionView.swift`;若后续决定采用,可直接用提案代码替换整个文件,并视需要做上述图标小改。 diff --git a/ios/WildGrowth/COMPLETION_最终交付_UI聚焦审查报告.md b/ios/WildGrowth/COMPLETION_最终交付_UI聚焦审查报告.md deleted file mode 100644 index 7702135..0000000 --- a/ios/WildGrowth/COMPLETION_最终交付_UI聚焦审查报告.md +++ /dev/null @@ -1,76 +0,0 @@ -# 完成页「最终交付 · UI 聚焦」审查报告(禁止应用) - -**审查对象**:仅实现完结页 UI + 统一分页,不碰数据排序与 isFirstNodeInChapter 判定逻辑的最终交付代码。 -**结论**:仅审查,不修改仓库内任何文件。核对「仅 UI、其他不变」及与当前仓库逻辑的一致性。 - ---- - -## 一、交付方承诺的「不变」项核对 - -| 承诺 | 本版代码 | 与当前仓库对比 | 结论 | -|------|----------|----------------|------| -| **loadMapData 不全局重排** | `realNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无 `.sorted` | 当前:`allCourseNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无排序 | ✅ 一致 | -| **isFirstNodeInChapter 保持原有逻辑** | `chapter.nodes.filter({ $0.status != .locked }).first`,无排序 | 当前:`validNodes = chapter.nodes.filter(...).sorted { $0.order < $1.order }`,再 `validNodes.first?.id == nodeId` | ⚠️ 见下 1.1 | -| **仅统一分页 + 完结页 UI** | 数据源改为 allItems (PlayerItem),最后一页渲染 CompletionView;其余 init/错误/toast/tabBar/handleBack 等保留 | — | ✅ 符合 | - -### 1.1 isFirstNodeInChapter 与「严格保持原有逻辑」的差异 - -- **当前仓库**:在 chapter 内先取 `validNodes = chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }`,再判断 `validNodes.first?.id == nodeId`,即「**按 order 排序后的第一章第一节**」。 -- **本版交付**:`chapter.nodes.filter({ $0.status != .locked }).first`,即「**数组顺序下的第一个未锁定节点**」,无 `.sorted { $0.order < $1.order }`。 - -因此:本版 **改动了** isFirstNodeInChapter 的判定规则(从「按 order 的首节」变为「按数组顺序的首节」)。若后端或产品依赖「按 order 的首节」显示章节标题,本版可能与现有行为不一致。 - -**建议**:若需 **严格保持原有判定规则**,应在 isFirstNodeInChapter 内保留与当前一致的写法: - -```swift -if chapter.nodes.contains(where: { $0.id == nodeId }) { - let validNodes = chapter.nodes - .filter { $0.status != .locked } - .sorted { $0.order < $1.order } - return validNodes.first?.id == nodeId -} -``` - -其余(loadMapData 不排序、仅统一分页与完结页 UI)本版已满足。 - ---- - -## 二、CompletionView 审查 - -- **职责**:仅展示(粉紫勋章、共完成 N 节、底部「回到我的内容」)+ `navStore.switchToGrowthTab()`,无 navigationPath、无数据处理。✅ -- **接口**:courseId / courseTitle / completedLessonCount 三参保留,与 CourseNavigation.completion 及三处 destination 兼容。✅ -- **替换方式**:全量替换 `Views/CompletionView.swift` 即可。✅ - -**结论**:CompletionView 符合「仅完结页 UI、其他不变」。 - ---- - -## 三、VerticalScreenPlayerView 审查 - -### 3.1 已符合「仅 UI + 统一分页、其他不变」 - -- **Init**:6 参未改,外部调用零影响。✅ -- **loadMapData**:flatMap + filter,无 sort;allItems = realNodes.map(.lesson) + append(.completion)。与当前「章节顺序 + 数组顺序」一致。✅ -- **错误态 / toast / hideTabBar / showTabBar / handleBack(path 优先)**:均保留。✅ -- **LessonPageView**:传 `self.courseTitle ?? mapData?.courseTitle`、navigationPath;headerConfig 仍用 isFirstNodeInChapter / getChapterTitle。✅ -- **CompletionView(内嵌)**:courseId、courseTitle、completedLessonCount。✅ -- **currentPositionProgress**:仅按 lesson 项计算,忽略完结页。✅ -- **合并范围**:说明中已注明仅替换主视图 Struct,保留 HeaderConfig、CourseProgressNavBar、LessonPageView 等。✅ - -### 3.2 唯一需确认处:isFirstNodeInChapter - -如上 1.1:当前仓库使用 **sorted { $0.order < $1.order }** 再取 first;本版使用 **.filter().first**(无排序)。若要求「严格保持原有逻辑、不修改判定规则」,需在替换时保留当前 isFirstNodeInChapter 实现(含 .sorted { $0.order < $1.order })。 - ---- - -## 四、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **loadMapData** | 无全局排序,与当前「章节顺序 + 数组顺序」一致。✅ | -| **isFirstNodeInChapter** | 本版为「数组顺序首节点」;当前为「按 order 排序后首节点」。若需与现有行为完全一致,需保留当前的 .sorted { $0.order < $1.order } 写法。⚠️ | -| **CompletionView** | 仅 UI + 退出导航,可全量替换。✅ | -| **VerticalScreenPlayerView** | 仅替换主视图 Struct,保留同文件其余类型;其他逻辑(错误/toast/tabBar/handleBack/传参)不变。✅ | -| **其他页面 / 功能** | 接口与调用方式未改,其他页面、其他功能不受影响。✅ | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_最终交付_isFirstNode回滚_审查反馈.md b/ios/WildGrowth/COMPLETION_最终交付_isFirstNode回滚_审查反馈.md deleted file mode 100644 index edc002f..0000000 --- a/ios/WildGrowth/COMPLETION_最终交付_isFirstNode回滚_审查反馈.md +++ /dev/null @@ -1,103 +0,0 @@ -# 最终交付代码审查反馈(isFirstNodeInChapter 完全回滚版) - -**审查对象**:您提供的「CompletionView 全量替换 + VerticalScreenPlayerView 仅替换 Struct」的最终交付代码,并已恢复 `isFirstNodeInChapter` 为含 `.sorted { $0.order < $1.order }` 的写法。 -**结论**:仅反馈,**不应用、不修改**仓库内任何文件。 - ---- - -## 参数说明(白话) - -| 说法 | 指什么 | 作用 | -|------|--------|------| -| **CompletionView 仍为 3 个参数** | 构造时仍是 `(courseId, courseTitle, completedLessonCount)` | GrowthView / ProfileView / DiscoveryView 里已有 `CompletionView(courseId: ..., courseTitle: ..., completedLessonCount: ...)` 的调用不用改;替换成新 UI 后接口不变,不会报错。 | -| **VerticalScreenPlayerView init 6 参** | 构造时仍是 `(courseId, nodeId, initialScrollIndex?, navigationPath?, isLastNode?, courseTitle?)` | MapView / GrowthView / DiscoveryView / ProfileView 等传入的 6 个参数(或只传前几项、后几项用默认值)都不用改;只改播放器内部实现,调用方零改动。 | - ---- - -## 应用后仅实现以下三点、且无多余修改 - -若您**只**做这两步:① 全量替换 `CompletionView.swift`;② 仅替换 `VerticalScreenPlayerView.swift` 里的 **struct VerticalScreenPlayerView**(保留同文件内 HeaderConfig、LessonPageView 等其余代码),则: - -| # | 需求 | 是否由本交付代码实现 | 说明 | -|---|------|----------------------|------| -| 1 | 播放器最后一个小节左滑进入完结页,从完结页右滑回到最后一个小节页 | ✅ 是 | 完结页作为 TabView 的最后一页内嵌在播放器内,左滑最后一节→完结页,右滑完结页→最后一节,无 push/pop。 | -| 2 | 完结页的 UI,以及从接口/数据获取「共完成多少个小节」 | ✅ 是 | 新 UI(粉紫勋章、点击点亮);数量来自 `UserManager.shared.studyStats.lessons`(应用内统计,通常由学习进度接口或本地完成逻辑更新)。 | -| 3 | 底部一个按钮,点击回到技能 Tab(我的课程列表) | ✅ 是 | 按钮「回到我的内容」仅调用 `navStore.switchToGrowthTab()`,切到技能 Tab。 | - -**其他没有任何多余修改**: - -- **只动 2 个文件**:`CompletionView.swift`(全量)、`VerticalScreenPlayerView.swift`(仅主 struct)。 -- **不改动**:CourseNavigation、MainTabView、MapView、ProfileView、DiscoveryView、GrowthView、NoteTreeView、NoteListView 等所有其他页面与类型;不增删导航枚举、不改 Tab 结构、不改地图/发现/个人/技能页逻辑。 -- 完结页不再通过「占位页 + onChange push .completion」出现,而是作为播放器 TabView 最后一页展示,因此无需也不会去改各 Tab 的 `navigationDestination(for: .completion)` 的写法(它们保留不动,只是从播放器内不再 push .completion)。 - -**结论**:应用本交付代码后,行为严格限于上述 1、2、3 三点,无其他多余修改。 - ---- - -## 一、isFirstNodeInChapter:与当前仓库 100% 一致 ✅ - -| 对比项 | 当前仓库实现 | 您提供的交付代码 | 结论 | -|--------|--------------|------------------|------| -| 章节内节点 | 先找到包含 `nodeId` 的 chapter,再在该章内取 `validNodes` | 遍历每个 chapter,取 `validNodes` | 语义等价 | -| 排序 | `chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }` | `chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }` | ✅ 完全一致 | -| 首节判定 | `validNodes.first?.id == nodeId` | `validNodes.first?.id == nodeId`(在含该 node 的章内) | ✅ 完全一致 | - -**结论**:章节判定逻辑已完全回滚到与当前仓库一致的写法(含 `.sorted { $0.order < $1.order }`),不会改变现有「按 order 的章节首节」展示行为。 - ---- - -## 二、loadMapData:无全局排序,与当前一致 ✅ - -- **当前仓库**:`allCourseNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无 `.sorted`。 -- **交付代码**:`realNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,仅 UI 层 `items = realNodes.map { .lesson($0) }` 再 `append(.completion)`,无全局排序。 - -**结论**:数据顺序与当前一致,未引入按 order 的整课排序。 - ---- - -## 三、CompletionView:仅 UI 更新,接口兼容 ✅ - -| 项目 | 说明 | -|------|------| -| 构造参数 | 仍为 `(courseId, courseTitle, completedLessonCount)`,GrowthView/ProfileView/DiscoveryView 等处已有调用无需改 | -| 依赖 | 仍使用 `@EnvironmentObject private var navStore: NavigationStore` | -| 按钮 | 「回到我的内容」仅调用 `navStore.switchToGrowthTab()`,未调用 `dismiss()` | -| 说明 | 在统一分页方案下,完结页作为 TabView 最后一页内嵌展示,无 push 栈,不调用 `dismiss()` 是正确行为 | - -**结论**:可全量替换 `CompletionView.swift`,仅 UI 从「翻牌 + 打字机」改为「粉紫勋章 + 点击点亮」,对外接口与行为符合预期。 - ---- - -## 四、VerticalScreenPlayerView:仅替换 Struct,其余保留 ✅ - -| 项目 | 结论 | -|------|------| -| **init** | 6 个构造参数完整保留(courseId, nodeId, initialScrollIndex?, navigationPath?, isLastNode?, courseTitle?),MapView/GrowthView 等调用方无需改 ✅ | -| **PlayerItem** | 枚举 `.lesson(MapNode)` / `.completion` 仅用于 UI 数据源,不参与完成数等业务逻辑 ✅ | -| **allItems** | 由 `realNodes` + 末尾 `.completion` 构成,无全局排序 ✅ | -| **currentPositionProgress** | 仅用 `lesson` 项计算,排除完结页,进度条正确 ✅ | -| **完结页展示** | 顶部进度条在 `currentNodeId == "COMPLETION_PAGE"` 时隐藏,逻辑正确 ✅ | -| **LessonPageView** | `courseTitle: self.courseTitle ?? mapData?.courseTitle` 可减少标题闪烁 ✅ | -| **错误态** | 保留加载失败 + 重试;交付代码中错误文案为 `.foregroundColor(.gray)`,当前为 `.inkSecondary`,属风格差异,可酌情统一 | -| **合并范围** | 仅替换 `struct VerticalScreenPlayerView { ... }` 及其内部的 `enum PlayerItem`;**必须保留**同文件内 `HeaderConfig`、`DuolingoProgressBar`、`CourseProgressNavBar`、`LessonPageView`、`LessonSkeletonView` 等所有其他类型,不得整文件覆盖 ✅ | - ---- - -## 五、对其他页面的影响 - -- **CourseNavigation**、**MainTabView**、**MapView**、**ProfileView**、**DiscoveryView**、**GrowthView** 等无需因本交付代码而改动。 -- 调用方仍按现有方式传入 6 参(含 `navigationPath?`、`isLastNode`、`courseTitle`);完结页由 TabView 内嵌展示,不再依赖 push `.completion` 或占位页 `onChange`。 - ---- - -## 六、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **isFirstNodeInChapter** | 已完全回滚为含 `.sorted { $0.order < $1.order }` 的写法,与当前仓库 100% 一致 ✅ | -| **loadMapData** | 无全局排序,与当前一致 ✅ | -| **CompletionView** | 仅 UI 更新,可全量替换 ✅ | -| **VerticalScreenPlayerView** | 仅替换主视图 Struct,保留同文件其余代码;逻辑与展示符合「逻辑回滚 + 统一分页」目标 ✅ | -| **其他页面** | 无需改动,零影响 ✅ | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_最终交付_逻辑回滚审查报告.md b/ios/WildGrowth/COMPLETION_最终交付_逻辑回滚审查报告.md deleted file mode 100644 index 76e3d15..0000000 --- a/ios/WildGrowth/COMPLETION_最终交付_逻辑回滚审查报告.md +++ /dev/null @@ -1,73 +0,0 @@ -# 完成页「最终交付 · 逻辑回滚」审查报告(禁止应用) - -**审查对象**:isFirstNodeInChapter 已恢复为含 `.sorted { $0.order < $1.order }` 的最终交付代码;确认不影响现有逻辑与展示。 -**结论**:仅审查,不修改仓库内任何文件。 - ---- - -## 一、isFirstNodeInChapter 与当前仓库一致性 - -| 项目 | 当前仓库 | 本版交付 | 结论 | -|------|----------|----------|------| -| **排序** | `validNodes = chapter.nodes.filter(...).sorted { $0.order < $1.order }` | 同:`.filter { $0.status != .locked }.sorted { $0.order < $1.order }` | ✅ 一致 | -| **首节判定** | `validNodes.first?.id == nodeId`(在「包含 nodeId 的 chapter」内) | `validNodes.first?.id == nodeId`(遍历每章,等价语义) | ✅ 等价 | - -本版写法:对每章取 `validNodes`(filter + sort by order),若 `validNodes.first?.id == nodeId` 则返回 true。 -当前写法:先找到包含 nodeId 的 chapter,再在该章内取 validNodes(filter + sort by order),返回 `validNodes.first?.id == nodeId`。 -二者语义相同:「nodeId 是否为其所在章内按 order 排序后的第一个未锁定节点」。 -**结论**:章节判定逻辑与现有实现保持 100% 一致,未改动核心逻辑。 - ---- - -## 二、loadMapData:无全局排序 - -- **本版**:`realNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无 `.sorted`。 -- **当前**:`allCourseNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无排序。 - -**结论**:保持「章节顺序 + 数组顺序」,未引入任何重排,与现有逻辑一致。 - ---- - -## 三、CompletionView - -- **职责**:仅 UI(粉紫勋章、共完成 N 节、底部「回到我的内容」)+ `navStore.switchToGrowthTab()`,无业务/数据逻辑。 -- **接口**:courseId / courseTitle / completedLessonCount 三参保留,与 CourseNavigation.completion 及三处 destination 兼容。 -- **操作**:全量替换 `Views/CompletionView.swift` 即可。 - -**结论**:仅完结页 UI 更新,不影响现有逻辑与展示。 - ---- - -## 四、VerticalScreenPlayerView - -### 4.1 已核对项 - -- **Init**:6 参未改,外部调用零影响。 -- **loadMapData**:flatMap + filter,无 sort;allItems = realNodes.map(.lesson) + append(.completion)。 -- **isFirstNodeInChapter**:含 `.sorted { $0.order < $1.order }`,与当前仓库等价。 -- **getChapterTitle**:与当前一致。 -- **currentPositionProgress**:仅按 lesson 项计算,忽略完结页。 -- **handleBack / 错误态 / toast / hideTabBar / showTabBar**:均保留。 -- **LessonPageView**:传 `self.courseTitle ?? mapData?.courseTitle`、navigationPath;headerConfig 仍用 isFirstNodeInChapter / getChapterTitle。 -- **CompletionView(内嵌)**:courseId、courseTitle、completedLessonCount。 - -### 4.2 合并范围(再次强调) - -- **仅替换** `struct VerticalScreenPlayerView { ... }` 及内部的 `enum PlayerItem`。 -- **不得删除** 同文件内的 HeaderConfig、DuolingoProgressBar、CourseProgressNavBar、LessonPageView、LessonSkeletonView、LessonErrorView、CompletionPlaceholderPage(或占位相关)等其余类型。 - -**结论**:在仅替换主视图 Struct 的前提下,现有逻辑与展示不受影响。 - ---- - -## 五、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **isFirstNodeInChapter** | 已恢复为含 `.sorted { $0.order < $1.order }` 的写法,与当前仓库语义一致,未改动核心判定逻辑。 | -| **loadMapData** | 无全局排序,保持原有顺序。 | -| **CompletionView** | 仅 UI 更新,可全量替换。 | -| **VerticalScreenPlayerView** | 仅替换主视图 Struct,保留同文件其余代码;其他逻辑与展示不变。 | -| **现有逻辑与展示** | 在按上述范围替换的前提下,不会受到影响。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_最终修正版_审查报告.md b/ios/WildGrowth/COMPLETION_最终修正版_审查报告.md deleted file mode 100644 index 70431d3..0000000 --- a/ios/WildGrowth/COMPLETION_最终修正版_审查报告.md +++ /dev/null @@ -1,89 +0,0 @@ -# 完成页「最终修正版」代码审查报告(禁止应用) - -**审查对象**:基于零影响审查报告修正后的 CompletionView + VerticalScreenPlayerView 最终版(参数优先、错误 Toast、排序逻辑、接口兼容)。 -**结论**:仅审查,不修改仓库内任何文件。核对三项修正与 6 条零影响条件,并说明应用时的合并范围。 - ---- - -## 一、三项修正落实情况 - -| 修正项 | 审查报告要求 | 本版代码 | 结论 | -|--------|----------------|----------|------| -| **参数优先权** | LessonPageView 传 `courseTitle ?? mapData?.courseTitle` | `courseTitle: self.courseTitle ?? mapData?.courseTitle`(LessonPageView 与 CompletionView 均用) | ✅ 已落实 | -| **错误处理** | loadMapData 失败时 `showToastMessage("加载失败")` | catch 内 `self.showToastMessage("加载失败")` | ✅ 已落实 | -| **排序逻辑** | isFirstNodeInChapter 用 `validNodes.sorted { $0.order < $1.order }` 再取 first | `validNodes = chapter.nodes.filter(...).sorted { $0.order < $1.order }`,再 `validNodes.first?.id == nodeId` | ✅ 已落实 | - ---- - -## 二、6 条零影响条件核对 - -| # | 条件 | 本版代码 | 结论 | -|---|------|----------|------| -| 1 | VerticalScreenPlayerView init 保持 6 参 | `init(courseId, nodeId, initialScrollIndex, navigationPath?, isLastNode?, courseTitle?)` 完整 | ✅ | -| 2 | 保留 CourseNavigation.completion 及三处 destination | 未改枚举与三 Tab;CompletionView 三参 | ✅ | -| 3 | CompletionView 保留三参 | courseId, courseTitle, completedLessonCount | ✅ | -| 4 | LessonPageView 传 courseTitle、navigationPath | `self.courseTitle ?? mapData?.courseTitle`,`navigationPath` 透传 | ✅ | -| 5 | 保留 loadError、toast、hideTabBar/showTabBar、handleBack(path 优先) | 错误态、toast、tabBar、handleBack 均保留;loadMapData 失败有 showToastMessage | ✅ | -| 6 | NoteTreeView / NoteListView 不修改 | init 未改,笔记流仍只传 3 参 | ✅ | - ---- - -## 三、CompletionView 审查 - -- **接口**:courseId / courseTitle / completedLessonCount 全保留,与 CourseNavigation.completion 及三处 destination 一致。✅ -- **内部**:纯 UI(粉紫勋章)+ handleReturnToRoot() → navStore.switchToGrowthTab(),无 navigationPath、无侧滑 Hack。✅ -- **视觉**:顶部「已完成」、底部「回到我的内容」淡蓝、无返回按钮。✅ - -**结论**:CompletionView 可直接全量替换 `Views/CompletionView.swift`。 - ---- - -## 四、VerticalScreenPlayerView 审查与合并范围 - -### 4.1 本版已包含且正确的部分 - -- Init 6 参、PlayerItem 枚举、allItems 数据源、loadMapData(realNodes + append .completion)、currentPositionProgress(仅 lesson)、handleBack(path 优先)、hideTabBar/showTabBar、showToastMessage、错误态 UI、toast、isFirstNodeInChapter(含 .sorted { $0.order < $1.order })、getChapterTitle。✅ - -### 4.2 应用时必须注意的合并范围(未应用,仅说明) - -当前 **VerticalScreenPlayerView.swift** 文件中除 `VerticalScreenPlayerView` 结构体外,还包含: - -- **HeaderConfig**、**DuolingoProgressBar**、**CourseProgressNavBar**(播放器依赖) -- **CompletionPlaceholderPage** 或 **LastPageSwipeModifier**(本方案中不再需要,可删除) -- **LessonPageView**、**LessonSkeletonView**、**LessonErrorView** 及后续所有类型与扩展 - -你提供的「最终修正版」只包含 **VerticalScreenPlayerView 结构体** 及 **PlayerItem** 枚举,未包含上述类型。因此: - -- **不能**用该片段整文件覆盖 **VerticalScreenPlayerView.swift**,否则会删掉 HeaderConfig、CourseProgressNavBar、LessonPageView 等,导致编译失败。 -- **正确做法**:在 **VerticalScreenPlayerView.swift** 内只做「局部替换」: - - 用本版的 **VerticalScreenPlayerView 结构体**(含 PlayerItem、body、loadMapData、handleBack、isFirstNodeInChapter 等)替换现有的 **VerticalScreenPlayerView** 结构体; - - 删除 **CompletionPlaceholderPage**(或占位页相关逻辑),不再添加 LastPageSwipeModifier; - - **保留** 文件内 **HeaderConfig**、**DuolingoProgressBar**、**CourseProgressNavBar**、**LessonPageView**、**LessonSkeletonView**、**LessonErrorView** 及之后所有内容不变。 - -### 4.3 realNodes 的 .sorted 与当前行为差异(可选核对) - -- **本版**:`let realNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }.sorted { $0.order < $1.order }` - 即对 **整课** 的节点列表按 `node.order` 做一次全局排序。 -- **当前实现**:`allCourseNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }` - 即 **不** 对 flatMap 后的列表排序,顺序为:先 chapter 顺序,再各 chapter 内 nodes 的数组顺序。 - -若后端 `node.order` 是 **按章节内** 的(例如每章都是 0,1,2,…),则对整课 flat 列表只按 `order` 排序会 **打乱章节顺序**(例如把各章 order=0 的节点排在一起)。若希望与当前行为完全一致,可考虑: - -- 要么 **去掉** realNodes 的 `.sorted { $0.order < $1.order }`,保持与当前一致的「章节顺序 + 章内数组顺序」; -- 要么在确认后端 `order` 为全局唯一或全局有序的前提下保留当前排序。 - -是否保留该 sort,可根据产品/后端约定决定;不影响三项修正与 6 条零影响条件。 - ---- - -## 五、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **三项修正** | 参数优先权、错误 Toast、isFirstNodeInChapter 排序均已落实。 | -| **6 条零影响** | 全部满足;对外接口与调用方无需改动。 | -| **CompletionView** | 可直接全量替换 `ios/WildGrowth/WildGrowth/Views/CompletionView.swift`。 | -| **VerticalScreenPlayerView** | 仅可替换 **结构体** 并删除占位页相关类型;必须保留同文件内 HeaderConfig、CourseProgressNavBar、LessonPageView 等所有其他类型,不能整文件覆盖。 | -| **realNodes 排序** | 与当前实现存在差异,是否保留 `.sorted { $0.order < $1.order }` 需结合后端 order 语义决定。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_粉紫掌机版_定稿审查报告.md b/ios/WildGrowth/COMPLETION_粉紫掌机版_定稿审查报告.md deleted file mode 100644 index 5cdb892..0000000 --- a/ios/WildGrowth/COMPLETION_粉紫掌机版_定稿审查报告.md +++ /dev/null @@ -1,78 +0,0 @@ -# CompletionView 粉紫掌机版 — 定稿审查报告(不应用) - -**审查日期**:2025-01-29 -**范围**:Pink-Purple Gameboy Edition(赛博粉紫外壳 + 黑框大屏 + 蓝色胶囊按钮 + 数字滚动) -**结论**:仅审查、不修改仓库;逻辑继承正确,有一处待确认的 UI 细节。 - ---- - -## 1. 修改点落实情况 - -| 修改点 | 要求 | 定稿实现 | 结论 | -|--------|------|----------|------| -| **外壳颜色** | 赛博粉紫渐变 (Hot Pink → Deep Purple) | `shellGradient`:`Color(1, 0.35, 0.8)` → `Color(0.58, 0.2, 0.9)` | ✅ | -| **去除装饰** | 右下角勋章删掉,只留数据 | `Spacer()` 替代原勋章位,注释 "已删除勋章" | ✅ | -| **屏幕布局** | 严格复刻截图 | 上:TOTAL → 数字 → LESSONS;下:FOCUS TIME → 数字 min;无 ONLINE/电量 | ✅ | -| **按钮** | 蓝色胶囊,与粉紫撞色 | 亮蓝渐变 + 凹槽 + 高光,保留撞色效果 | ✅ | -| **动效** | 点击 → 屏幕闪白 → 数字匀速滚动 | `performActivation` 中 `isSystemOn = true` + `withAnimation(.linear(duration: 1.5))` | ✅ | - ---- - -## 2. 逻辑继承(FRC 要求) - -| 项目 | 要求 | 定稿实现 | 结论 | -|------|------|----------|------| -| **持久化 Key** | `has_revealed_course_\(courseId)` | 一致 | ✅ | -| **游客短路** | 不调网络,短延迟后直接激活 | `if userManager.isGuest { asyncAfter(0.5) { performActivation(0,0) }; return }` | ✅ | -| **游客延迟** | 极短假连接 | 0.5s | ✅ | -| **登录用户** | 拉取 fetchUserProfile 后激活 | `Task { sleep(0.8s); fetchUserProfile; performActivation(lessons, minutes) }` | ✅ | - ---- - -## 3. RollingNumberText 与 Animatable - -- `RollingNumberText` 正确实现 `Animatable`:`animatableData` 读写 `value`,`Double` 符合 `VectorArithmetic`,数字在 1.5s 内线性插值显示。 -- `displayLessons`、`displayMinutes` 初始为 0,激活时设为目标值,配合 `withAnimation(.linear(duration: 1.5))` 实现匀速滚动。 - ---- - -## 4. checkSystemStatus 与数据来源 - -- 已激活时:`displayLessons`、`displayMinutes` 从 `userManager.studyStats` 取,保证二次进入时显示正确。 -- 游客:`performActivation(0, 0)`,与未登录、无服务端统计一致。 -- 登录用户:`performActivation(lessons, minutes)` 使用 `fetchUserProfile` 后的 `studyStats`,正确。 - ---- - -## 5. 待确认 UI 细节 - -**按钮未按下时的图标**:当前在 `!isBooting && !isSystemOn` 时显示 `Image(systemName: "checkmark")`,与激活后相同。若希望更易理解「待操作」,可改为 `"arrow.down.circle.fill"` 或 `"checkmark.circle"` 等,属可选优化,非阻塞。 - ---- - -## 6. 可选小修正(按需) - -- **ForEach(0..<100)**:扫描线纹理的 `ForEach(0..<100)` 若在部分 Swift/SwiftUI 版本报错,可加上 `id: \.self`。 - ---- - -## 7. 接口与影响范围 - -| 项目 | 结论 | -|------|------| -| **初始化参数** | 三参数不变,调用方无需修改 | -| **替换方式** | 直接整文件替换 `CompletionView.swift` | -| **其他页面** | 无影响 | - ---- - -## 8. 总结 - -| 维度 | 结论 | -|------|------| -| **视觉修改** | ✅ 粉紫外壳、去勋章、复刻布局、蓝胶囊按钮、数字滚动 | -| **逻辑继承** | ✅ Key 回滚、游客短路、登录用户拉取逻辑一致 | -| **动效** | ✅ 屏幕闪白 + 1.5s 线性滚动 | -| **接口兼容** | ✅ 可直接替换 | - -**审查结论**:定稿代码满足设计要求,逻辑继承正确,可直接用于替换。**本次未对仓库做任何修改。** diff --git a/ios/WildGrowth/COMPLETION_给Gemini的反馈_不改逻辑层.md b/ios/WildGrowth/COMPLETION_给Gemini的反馈_不改逻辑层.md deleted file mode 100644 index 3999e44..0000000 --- a/ios/WildGrowth/COMPLETION_给Gemini的反馈_不改逻辑层.md +++ /dev/null @@ -1,42 +0,0 @@ -# 给 Gemini 的反馈:完结页不要改逻辑层 - -## 核心要求 - -**坚持「点击按钮时拉后端」**,不要改逻辑层。 - ---- - -## 1. 数据流(必须保持) - -- **当前逻辑**:用户点击「标记完成」/「上传学习数据」等按钮 → **在 CompletionView 内部**调用后端拉取最新数据(如 `UserManager.shared.fetchUserProfile()`)→ 用拉取结果(如 `UserManager.shared.studyStats.lessons` / `.time`)展示。 -- **要求**:完结页的**数据来源与拉取时机**保持上述逻辑,即**点击按钮时在页面内拉后端**,不在父视图提前传「已完成小节数、专注时长」等业务数据。 - ---- - -## 2. 不要做的改动(逻辑层) - -- **不要**让 CompletionView 增加必选参数 `completedLessonCount`、`focusMinutes` 等由父视图传入的「业务数据」。 -- **不要**把「拉取最新统计」的责任从 CompletionView 挪到父视图(如 VerticalScreenPlayerView)或改成「进入页面时父视图传参」。 -- **不要**改变「点击按钮 → 调接口 → 用接口/本地统计结果展示」这一套流程;可以保留「上传/显影」的**交互与动效**,但**显影后展示的数据必须来自点击后拉取的结果**(例如继续用 UserManager.studyStats 或等价数据源)。 - ---- - -## 3. 可以做的(仅 UI / 视觉) - -- 可以改完结页的**视觉与动效**(例如拍立得、赛博海报、骨架、显影动画等)。 -- 可以改**按钮文案**(如「上传学习数据」「标记完成」)和**排版**(顶对齐、大数字等)。 -- 可以保留 **UserDefaults 显影状态**(已显影过则直接展示结果态,不重复播动画),只要结果态展示的数据仍来自**点击时拉取的后端/统计**(例如首次显影时点按钮拉取,之后用本地缓存或同一数据源)。 -- **回到我的内容**:必须调用 `navStore.switchToGrowthTab()`,回到技能 Tab。 - ---- - -## 4. 接口约定(建议) - -- CompletionView 的入参保持与现有一致或仅做**可选**扩展(如可选 `navigationPath`),**不要**新增必选的「业务数据」参数(如 `completedLessonCount: Int`、`focusMinutes: Int`)。 -- 小节数、专注时长等**在 CompletionView 内部**从现有数据源获取(如 UserManager.studyStats),并在**用户点击按钮后**通过现有或约定的接口拉取最新再展示。 - ---- - -## 5. 一句话总结 - -**只改完结页的 UI/动效/文案,不要改「点击按钮时拉后端、用拉取结果展示」的逻辑与数据层;不要通过父视图传入 completedLessonCount、focusMinutes 等业务数据。** diff --git a/ios/WildGrowth/COMPLETION_统一分页_零影响审查报告.md b/ios/WildGrowth/COMPLETION_统一分页_零影响审查报告.md deleted file mode 100644 index 12738cc..0000000 --- a/ios/WildGrowth/COMPLETION_统一分页_零影响审查报告.md +++ /dev/null @@ -1,122 +0,0 @@ -# 完成页「统一分页架构」— 零影响审查报告(禁止应用) - -**审查目标**:若应用此方案,确保**其他页面、其他功能、其他逻辑一点也不受影响**。 -**结论**:仅审查,不修改仓库内任何文件。下文列出所有受影响点及达成「零影响」的**必要条件**。 - ---- - -## 一、方案会动到的文件与接口 - -| 文件 | 方案中的改动 | -|------|----------------| -| **CompletionView.swift** | 整文件替换:去掉 courseId;仅保留 courseTitle、completedLessonCount;纯 UI + switchToGrowthTab | -| **VerticalScreenPlayerView.swift** | 数据源改为 allItems (PlayerItem);去掉占位页与 onChange;init 去掉 navigationPath、isLastNode、courseTitle;handleBack 仅 dismiss();LessonPageView 传参可能变 | - ---- - -## 二、依赖关系与「零影响」条件 - -### 2.1 谁调用 VerticalScreenPlayerView? - -| 调用方 | 当前传参 | 方案中 init 若改为 3 参会怎样 | -|--------|----------|------------------------------| -| **GrowthView** (.player) | courseId, nodeId, initialScrollIndex: nil, **navigationPath: $growthPath**, **isLastNode**, **courseTitle** | ❌ 编译失败(多传 3 个参数) | -| **ProfileView** (.player) | 同上,navigationPath: $profilePath | ❌ 同上 | -| **DiscoveryView** (.player) | 同上,navigationPath: $homePath | ❌ 同上 | -| **NoteTreeView** (.player) | courseId, nodeId, initialScrollIndex(无 path / isLastNode / courseTitle) | ✅ 仍兼容 3 参 init | - -**零影响条件 1**: -**VerticalScreenPlayerView 的 init 必须保留现有 6 参签名**(courseId, nodeId, initialScrollIndex, navigationPath?, isLastNode?, courseTitle?),且默认值不变。内部可改用 allItems + PlayerItem,但对外接口不变,这样 GrowthView / ProfileView / DiscoveryView **无需改一行**。 - ---- - -### 2.2 谁使用 CourseNavigation 与 CompletionView? - -| 位置 | 当前行为 | 方案若不再 push .completion 会怎样 | -|------|----------|-------------------------------------| -| **GrowthView** | navigationDestination(.completion) → CompletionView(courseId, courseTitle, completedLessonCount) | 从「课程流」进完成页时不再走此分支(完成页在 TabView 内);若保留此分支,深链/通知若 push .completion 仍能展示 | -| **ProfileView** | 同上 | 同上 | -| **DiscoveryView** | 同上 | 同上 | -| **MapView** | 只 append .player,不直接 append .completion | ✅ 无影响 | -| **VerticalScreenPlayerView**(当前) | 滑到占位页时 append .completion | 方案中不再 append,完成页在 TabView 内 | - -**零影响条件 2**: -- **保留** `CourseNavigation.completion` 与三处 `.completion` 的 navigationDestination。 -- **CompletionView 仍保留三参**:courseId, courseTitle, completedLessonCount。这样: - - 从课程流进入时,完成页由播放器内 TabView 展示,不经过 navigationDestination; - - 若有深链/通知/其他入口直接 push .completion,三处 destination 仍能正确展示 CompletionView,且接口一致。 - ---- - -### 2.3 LessonPageView 的传参 - -| 当前传参 | 方案中若改为不传 navigationPath / 不传 courseTitle 会怎样 | -|----------|-----------------------------------------------------------| -| courseId, nodeId, currentGlobalNodeId, initialScrollIndex, headerConfig, **courseTitle**, **navigationPath** | LessonPageView 中两者均为可选;不传则 nil。若内部有逻辑依赖 path 或 courseTitle,可能受影响。 | - -**零影响条件 3**: -在「统一分页」的播放器内部,对 **LessonPageView** 的调用仍传入 **courseTitle**、**navigationPath**(用 VerticalScreenPlayerView 持有的 courseTitle、navigationPath),与当前一致。即:仅改数据源与最后一页渲染方式,不减少对 LessonPageView 的传参。 - ---- - -### 2.4 播放器内部逻辑(其他功能) - -| 当前能力 | 方案中若省略会怎样 | -|----------|--------------------| -| **handleBack** 用 navigationPath.removeLast() 或 dismiss() | 若改为仅 dismiss(),在 NavigationStack 内效果通常等价(pop 一层)。为保险起见,建议保留:有 path 且非空时 removeLast(),否则 dismiss()。 | -| **loadError / 错误态 UI** | 方案片段中未写,若删除则错误态消失。 | -| **showToast / toastMessage** | 同上,若删除则 toast 能力消失。 | -| **hideTabBar / showTabBar(onAppear / onDisappear)** | 若删除则播放器出现时 TabBar 可能仍显示。 | -| **currentPositionProgress**(不含占位页) | 方案中有「进度计算忽略完结页」,逻辑等价即可。 | -| **GeometryReader 等布局** | 若删除可能影响布局,建议保留与当前一致。 | - -**零影响条件 4**: -播放器内**保留**:loadError / 错误态 UI、showToast / toastMessage、hideTabBar / showTabBar、handleBack 的 path 优先逻辑(若保留 navigationPath 参数则一并保留)、以及现有布局与进度计算方式(仅把「占位页」换成虚拟 completion 页,进度仍只按真实小节算)。 - ---- - -### 2.5 笔记流(NoteTreeView / NoteListView) - -| 当前 | 说明 | -|------|------| -| 使用 NoteNavigationDestination.player,传 courseId, nodeId, initialScrollIndex | 不传 navigationPath、isLastNode、courseTitle;当前 init 中这些为可选且带默认值。 | - -**零影响条件 5**: -保持 VerticalScreenPlayerView 的 init 中 **navigationPath、isLastNode、courseTitle 为可选且默认 nil**。这样 NoteTreeView / NoteListView **无需任何修改**,行为不变(不传 path 则不会 push 完成页;统一分页下完成页在 TabView 内,笔记流仍不涉及 .completion 路由)。 - ---- - -### 2.6 其他引用 - -| 引用 | 影响 | -|------|------| -| **ContentBlockBuilder** 注释「与 VerticalScreenPlayerView 保持一致」 | 仅注释,不依赖接口或类型。 | -| **CourseNavigation** 枚举 | 保留 .map / .player / .completion 三 case,见零影响条件 2。 | -| **MapView** 中 append CourseNavigation.player(courseId, nodeId, isLastNode, courseTitle) | 不依赖播放器 init 是否接收这些参数,只要枚举不变即无影响。 | - ---- - -## 三、零影响检查清单(应用前必达) - -若要在「其他页面、其他功能、其他逻辑一点也不受影响」的前提下应用统一分页方案,需同时满足: - -| # | 条件 | 说明 | -|---|------|------| -| 1 | VerticalScreenPlayerView **init 保持 6 参**(courseId, nodeId, initialScrollIndex, navigationPath?, isLastNode?, courseTitle?) | GrowthView / ProfileView / DiscoveryView 不改动 | -| 2 | 保留 **CourseNavigation.completion** 及三处 **.completion** 的 navigationDestination | 深链/其他入口 push .completion 仍可用 | -| 3 | **CompletionView 保留三参**(courseId, courseTitle, completedLessonCount) | 与现有 destination 及枚举一致 | -| 4 | 播放器内对 **LessonPageView** 仍传 **courseTitle**、**navigationPath**(用播放器自身属性) | LessonPageView 行为不变 | -| 5 | 播放器内保留 **loadError、toast、hideTabBar/showTabBar、handleBack(path 优先)** 及现有布局/进度逻辑 | 错误、提示、TabBar、返回、进度均不受影响 | -| 6 | **NoteTreeView / NoteListView** 不修改 | 依赖 init 可选参数,已满足则零影响 | - ---- - -## 四、总结 - -- **按方案原文直接替换**(init 改为 3 参、CompletionView 去掉 courseId、播放器去掉 path/错误/toast/tabBar 等):**会**影响 GrowthView / ProfileView / DiscoveryView(编译或行为)、以及错误态/toast/TabBar/返回等逻辑。 -- **在满足上述 6 条零影响条件的前提下**,再引入 allItems + PlayerItem、TabView 最后一页渲染 CompletionView、不再 push .completion,则: - - 其他页面(含三 Tab、MapView、笔记流)**无需改**; - - 其他功能(错误、toast、TabBar、返回、进度、深链 .completion)**不受影响**; - - 其他逻辑(完课统计、LessonPageView、CourseNavigation)**保持一致**。 - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_统一分页_零影响版代码审查报告.md b/ios/WildGrowth/COMPLETION_统一分页_零影响版代码审查报告.md deleted file mode 100644 index 09b3084..0000000 --- a/ios/WildGrowth/COMPLETION_统一分页_零影响版代码审查报告.md +++ /dev/null @@ -1,97 +0,0 @@ -# 完成页「统一分页 + 零影响」代码审查报告(禁止应用) - -**审查对象**:严格遵守 6 条零影响条件的 CompletionView + VerticalScreenPlayerView 完整代码(内部重构,外部兼容)。 -**结论**:仅审查,不修改仓库内任何文件。对照 6 条逐项核对,并列出需补全/修正的细节以达「绝对零影响」。 - ---- - -## 一、6 条零影响条件对照 - -| # | 条件 | 本版代码 | 结论 | -|---|------|----------|------| -| 1 | VerticalScreenPlayerView init 保持 6 参 | `init(courseId, nodeId, initialScrollIndex, navigationPath?, isLastNode?, courseTitle?)` 完整保留 | ✅ 满足 | -| 2 | 保留 CourseNavigation.completion 及三处 destination | 未改枚举与三 Tab;CompletionView 三参,destination 调用不变 | ✅ 满足 | -| 3 | CompletionView 保留三参 | `courseId`, `courseTitle`, `completedLessonCount` 均有 | ✅ 满足 | -| 4 | LessonPageView 仍传 courseTitle、navigationPath | 传 `courseTitle: mapData?.courseTitle`、`navigationPath: navigationPath` | ⚠️ 见下 4.1 | -| 5 | 保留 loadError、toast、hideTabBar/showTabBar、handleBack(path 优先) | 错误态、toast、tabBar、handleBack 均保留 | ⚠️ 见下 5.1、5.2 | -| 6 | NoteTreeView / NoteListView 不修改 | init 未改,笔记流仍只传 3 参 | ✅ 满足 | - ---- - -## 二、CompletionView 审查 - -- **接口**:courseId / courseTitle / completedLessonCount 全保留,与 CourseNavigation.completion 及三处 destination 一致。✅ -- **内部**:纯 UI(粉紫勋章)+ navStore.switchToGrowthTab(),无 navigationPath、无侧滑 Hack。✅ -- **视觉**:顶部「已完成」、底部「回到我的内容」淡蓝、无返回按钮。与需求一致。✅ -- **无遗漏**:无依赖 dismiss、无依赖 path,作为 TabView 一页或作为 push 目标均可。✅ - -**结论**:CompletionView 满足零影响条件,无需改动。 - ---- - -## 三、VerticalScreenPlayerView 审查 - -### 3.1 已满足项 - -- **Init**:6 参完整,与现有 GrowthView / ProfileView / DiscoveryView / NoteTreeView / NoteListView 调用兼容。✅ -- **PlayerItem**:lesson(MapNode) + completion,id 分别为 node.id 与 `"COMPLETION_PAGE"`。✅ -- **loadMapData**:realNodes + append(.completion),allItems 构造正确;loadError = nil 与 catch 内 set loadError 均有。✅ -- **currentPositionProgress**:仅用 lesson 项计算,完结页时返回 1.0,逻辑正确。✅ -- **handleBack**:path 非空则 removeLast(),否则 dismiss()。✅ -- **hideTabBar / showTabBar / toast**:保留。✅ -- **LessonPageView**:传 courseId, nodeId, currentGlobalNodeId, initialScrollIndex, headerConfig, courseTitle, navigationPath。✅ -- **CompletionView(内嵌)**:传 courseId, courseTitle, completedLessonCount。✅ - -### 3.2 需补全或修正的细节(达「绝对零影响」) - -#### 4.1 LessonPageView 的 courseTitle 传参(对应条件 4) - -- **本版**:`courseTitle: mapData?.courseTitle`(仅用加载后的 mapData)。 -- **当前实现**:`courseTitle: courseTitle`(用调用方传入的 courseTitle,如 MapView 的 data.courseTitle)。 -- **差异**:加载完成前 mapData 为 nil,本版会传 nil;当前实现一进入就有值。若希望与现有行为完全一致,建议传 **`courseTitle ?? mapData?.courseTitle`**,优先用传入值,再回退到加载结果。 - -#### 5.1 loadMapData 失败时的 Toast(对应条件 5) - -- **当前实现**:catch 中除 set loadError 外,还调用 `showToastMessage("加载失败")`。 -- **本版**:catch 中只 set loadError,未调用 showToastMessage。 -- **建议**:在 catch 的 MainActor.run 内补上 **`showToastMessage("加载失败")`**,与现有体验一致。 - -#### 5.2 isFirstNodeInChapter 实现(对应条件 5:逻辑不变) - -- **当前实现**:在 chapter 内取 `validNodes = chapter.nodes.filter(locked).sorted { $0.order < $1.order }`,再 `validNodes.first?.id == nodeId`(即按 **order** 排序后的「第一章第一节」)。 -- **本版**:`chapter.nodes.filter(locked).first`,未按 order 排序,相当于用数组顺序的「第一个」。 -- **风险**:若后端/本地 chapter.nodes 顺序与 order 不一致,本版可能与当前表现不同。 -- **建议**:与当前保持一致,使用 **`validNodes = chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }`,再 `validNodes.first?.id == nodeId`**。 - -#### 5.3 其他可选一致性(非必须) - -- **空状态文案**:当前为「暂无内容」+「该课程还没有可用的学习内容」;本版为「暂无内容」+ 单行。若需完全一致可补副标题,否则可保留本版简化。 -- **GeometryReader**:当前 body 最外层包了一层 GeometryReader;本版未包。若当前无依赖 geo 的布局,可不再加;若有,需保留或等价处理。 -- **Import**:hideTabBar/showTabBar 使用 UIApplication,需 **import UIKit**。若文件当前已含 UIKit 则无需改;否则需补。 - ---- - -## 四、对外暴露与调用方 - -| 调用方 / 入口 | 是否需改 | 说明 | -|---------------|----------|------| -| GrowthView / ProfileView / DiscoveryView(.player / .completion) | 否 | init 与 CompletionView 三参未变 | -| MapView(append .player) | 否 | 枚举与参数不变 | -| NoteTreeView / NoteListView(.player) | 否 | 仍只传 3 参,可选参默认 nil | -| CourseNavigation 枚举 | 否 | 未改 | -| navigationDestination(.completion) | 否 | 仍用 CompletionView(courseId, courseTitle, completedLessonCount) | - -**结论**:在补全 4.1、5.1、5.2 后,对外接口与所有调用方均可保持零改动、零行为差异。 - ---- - -## 五、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **6 条零影响** | 条件 1、2、3、6 已满足;条件 4、5 在按 3.2 补全后可达「绝对零影响」。 | -| **CompletionView** | 可直接采用,无需改。 | -| **VerticalScreenPlayerView** | 建议补全:① LessonPageView 传 `courseTitle ?? mapData?.courseTitle`;② loadMapData 失败时 `showToastMessage("加载失败")`;③ isFirstNodeInChapter 按 order 排序取 first。 | -| **其他页面 / 功能 / 逻辑** | 在以上补全前提下,其他页面、其他功能、其他逻辑均不受影响。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_统一分页方案_审查报告.md b/ios/WildGrowth/COMPLETION_统一分页方案_审查报告.md deleted file mode 100644 index c894d09..0000000 --- a/ios/WildGrowth/COMPLETION_统一分页方案_审查报告.md +++ /dev/null @@ -1,64 +0,0 @@ -# 完成页「统一分页架构」— 审查报告(禁止应用) - -**审查对象**:虚拟节点 (PlayerItem.completion) 作为 TabView 最后一页,CompletionView 内嵌于播放器;是否导致「完成小节统计虚高」。 -**结论**:仅审查,不修改仓库内任何文件。 - ---- - -## 一、核心问题:会不会导致完成小节统计虚高? - -**结论:不会。** - -### 1.1 数据流梳理 - -| 环节 | 来源 / 行为 | 是否计入「完成小节」 | -|------|-------------|----------------------| -| **展示数字** | CompletionView 显示的 `completedLessonCount` 来自 `UserManager.shared.studyStats.lessons` | 仅展示,不写入 | -| **统计来源** | `studyStats.lessons` 由后端/用户接口(如 `completed_lessons`)拉取并写入 UserManager | 后端口径为真实完课数 | -| **完课上报** | 仅在 **LessonPageView** 内,当该节进度 ≥ 1.0 时调用 `LearningService.shared.completeLesson(nodeId: ...)` | 只对**真实课程节点**上报 | -| **虚拟节点** | `PlayerItem.completion` 仅存在于前端的 `allItems`,id 为 `"COMPLETION_PAGE"` | 不参与任何完课 API,无 nodeId | - -### 1.2 为何不会虚高 - -- **总结页不是一节课**:虚拟节点只用于 TabView 的「最后一页」展示,没有对应的 `MapNode`,也不会调用 `completeLesson(nodeId:)`。 -- **完课只发生在 LessonPageView**:只有渲染 `PlayerItem.lesson(node)` 时才会加载该节的进度、在达到条件时上报完课;渲染 `PlayerItem.completion` 时只渲染 CompletionView,没有任何完课或统计写入逻辑。 -- **展示用既有统计**:CompletionView 只是读取并展示 `UserManager.shared.studyStats.lessons`,不修改该值;该值的增加只来自真实小节在 LessonPageView 中的完课上报与后端同步。 - -因此:**把总结页当作 TabView 最后一页、用虚拟节点渲染 CompletionView,不会把总结页算成一节,也不会导致完成小节统计虚高。** - ---- - -## 二、方案本身审查(与当前实现对比) - -### 2.1 优点 - -- **交互统一**:左滑/右滑完全由 TabView(UIScrollView)负责,无需自定义 DragGesture、无需 SwipeBackEnabler。 -- **CompletionView 职责单一**:只做 UI 与「回到技能页根」按钮,不碰 navigationPath、不碰侧滑 Hack。 -- **状态集中**:完结页是 `PlayerItem` 枚举的一支,与课程节点同属一个数据源,逻辑清晰。 - -### 2.2 需注意的改动(若将来应用) - -1. **VerticalScreenPlayerView 的 init** - 方案中去掉了 `navigationPath`、`isLastNode`、`courseTitle`。当前 **GrowthView / ProfileView / DiscoveryView** 的 `.player` 分支会传这三个参数;若直接按方案改 init,调用方会报错。需要二选一: - - 保留这三个参数(兼容现有调用),或 - - 同时改三处调用方,不再传这些参数。 - -2. **CompletionView 的接口** - 方案中 CompletionView 只有 `courseTitle` 和 `completedLessonCount`(无 `courseId`)。若项目中别处仍通过 `CourseNavigation.completion(courseId: ...)` 等 push 进 CompletionView,则需保留 `courseId` 或同步改那些入口。 - -3. **进度条与 TabBar** - 当前实现有 `currentPositionProgress`(不含占位页)、hideTabBar/showTabBar、loadError、toast 等。方案里进度条在 `currentNodeId == "COMPLETION_PAGE"` 时隐藏,其余若省略需确认是否要保留(如错误态、toast、tabBar 隐藏)。 - -4. **笔记流** - NoteTreeView / NoteListView 里用的 VerticalScreenPlayerView 目前传的是 `NoteNavigationDestination.player`,不传 navigationPath。若播放器 init 去掉 navigationPath,笔记流不受影响;若保留可选 navigationPath 以兼容,也需在方案里写明。 - ---- - -## 三、审查结论汇总 - -| 问题 | 结论 | -|------|------| -| **会不会导致完成小节统计虚高?** | **不会**。总结页只是虚拟的一页,用于展示;完课统计只由 LessonPageView 对真实节点调用 completeLesson 产生,CompletionView 仅读取 studyStats.lessons 做展示。 | -| 统一分页架构本身 | 交互简单、无需手势与侧滑 Hack,CompletionView 可做纯 UI;若应用需处理 init 与调用方兼容、以及错误/toast/tabBar 等现有能力是否保留。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_赛博拍立得Final_审查报告.md b/ios/WildGrowth/COMPLETION_赛博拍立得Final_审查报告.md deleted file mode 100644 index 07c134a..0000000 --- a/ios/WildGrowth/COMPLETION_赛博拍立得Final_审查报告.md +++ /dev/null @@ -1,55 +0,0 @@ -# 「赛博拍立得 Final」版 CompletionView 审查报告(禁止应用) - -**审查对象**:Gemini 提供的 Cyber Polaroid Final 版 CompletionView(拍立得隐喻、UserDefaults 显影状态、navStore.switchToGrowthTab()、真实数据由参数传入)。 -**结论**:仅审查,**不应用、不修改**仓库内任何文件。 - ---- - -## 一、需求符合性 - -| 项目 | 需求/说明 | 本版实现 | 结论 | -|------|-----------|----------|------| -| **回到我的内容** | 必须调用 `navStore.switchToGrowthTab()`,回到技能 Tab | `handleBackToContent()` 内先 `navStore.switchToGrowthTab()`,再清 path 或 dismiss | ✅ 符合 | -| **真实数据** | 小节数、专注时长由父视图传入,不写死 | `completedLessonCount`、`focusMinutes` 均为 `let`,由调用方传入 | ✅ 符合 | -| **状态持久化** | 用 UserDefaults 记录「该课程已显影」 | `storageKey = "has_revealed_course_\(courseId)"`,显影后 `set(true)`,onAppear 时 `checkDevelopmentStatus()` | ✅ 符合 | -| **视觉与交互** | 未显影 → 上传/显影 → 赛博海报;顶对齐、大数字 | UndevelopedFilm + DevelopedPoster,排版与说明一致 | ✅ 符合 | - ---- - -## 二、接口变更与对「其他页面」的影响 - -| 项目 | 说明 | -|------|------| -| **CompletionView 入参** | 本版为 **必选**:`courseId`, `courseTitle`, `completedLessonCount`, `focusMinutes`;**可选**:`navigationPath`。 | -| **当前调用处** | 仓库内仅 **VerticalScreenPlayerView** 一处构造 CompletionView,当前传参为:`courseId`, `courseTitle`, `navigationPath`(**未传** `completedLessonCount`、`focusMinutes`)。 | -| **影响** | 若只替换 CompletionView.swift 而**不修改调用方**,会因缺少 `completedLessonCount`、`focusMinutes` 两个必选参数而**编译失败**。 | - -因此:**必须同时修改 VerticalScreenPlayerView** 中构造 CompletionView 的那一行,补上: - -- `completedLessonCount: UserManager.shared.studyStats.lessons`(或你项目里等价的数据源) -- `focusMinutes: UserManager.shared.studyStats.time`(或 0,若暂无专注时长) - -**其他页面**:当前无其它地方使用 CompletionView,CourseNavigation 也无 `.completion` case,故除 VerticalScreenPlayerView 这一处调用外,**无需改其它页面**;逻辑与展示也不受影响。 - ---- - -## 三、小结:是否「只动完结页」 - -| 维度 | 结论 | -|------|------| -| **仅替换 CompletionView.swift** | ❌ 不够。本版多了两个必选参数,**必须**在 VerticalScreenPlayerView 中补传 `completedLessonCount` 与 `focusMinutes`,否则无法编译。 | -| **替换 CompletionView + 修改 VerticalScreenPlayerView 内一行调用** | ✅ 可做到。其它页面(GrowthView / ProfileView / MapView / CourseNavigation 等)逻辑与展示均不受影响。 | -| **若希望零改动调用方** | 可将 `focusMinutes` 改为带默认值,例如 `focusMinutes: Int = 0`;`completedLessonCount` 若希望与现有「由父视图传入」一致,建议保留必选并由 VerticalScreenPlayerView 传入。 | - ---- - -## 四、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **需求** | 回到技能 Tab、真实数据由参数传入、UserDefaults 持久化、拍立得交互与排版均符合说明。 | -| **接口** | 新增必选参数 `completedLessonCount`、`focusMinutes`,**会**影响当前唯一调用方 VerticalScreenPlayerView,需补参。 | -| **其他页面** | 仅 VerticalScreenPlayerView 需改一行调用;其余页面与逻辑、展示均不受影响。 | -| **建议** | 不应用本报告所述代码;若采用本版,需同步在 VerticalScreenPlayerView 中为 CompletionView 传入 `completedLessonCount` 与 `focusMinutes`。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_赛博拍立得版_审查报告.md b/ios/WildGrowth/COMPLETION_赛博拍立得版_审查报告.md deleted file mode 100644 index 27ae8cd..0000000 --- a/ios/WildGrowth/COMPLETION_赛博拍立得版_审查报告.md +++ /dev/null @@ -1,51 +0,0 @@ -# 「赛博拍立得」版 CompletionView 审查报告(禁止应用) - -**审查对象**:Gemini 提供的 Cyber Polaroid 版 CompletionView(拍立得隐喻、上传学习数据、hasUploadedData 状态持久化)。 -**结论**:仅审查,**不应用、不修改**仓库内任何文件。 - ---- - -## 一、与当前需求的偏差 - -| 项目 | 当前需求 / 现有约定 | Gemini 本版实现 | 结论 | -|------|---------------------|-----------------|------| -| **底部按钮语义** | 点击「回到我的内容」应**回到技能 Tab(我的课程列表)**,即 `navStore.switchToGrowthTab()` | `handlePopToRoot()` 仅做 `path.wrappedValue = NavigationPath()` 或 `dismiss()`,**未切到技能 Tab** | ❌ 行为不符:从发现/个人 Tab 进入完结页时,点按钮只会清栈或 dismiss,仍停留在当前 Tab | -| **数据来源** | 共完成小节数来自接口/本地统计(如 `UserManager.shared.studyStats.lessons`),仅展示 | 新增 `focusMinutes: Int = 45` 写死;显影态展示「专注时长 (MIN)」,数据非来自现有统计接口 | ⚠️ 若产品无「专注时长」需求,属多余展示;若有,需接真实数据源 | -| **完结页入口** | 作为 TabView 最后一页内嵌,无「上传」步骤,仅展示 + 回到技能 Tab | 未显影 → 点击「上传学习数据」→ 模拟 1.5s 显影 → 显影态;依赖 `hasUploadedData` 状态 | ❌ 交互模型与当前「统一分页 + 纯展示」不一致,易与真实学习数据上报逻辑混淆 | - ---- - -## 二、接口与调用方影响 - -| 项目 | 说明 | -|------|------| -| **CompletionView 入参** | Gemini 版为 `courseId, courseTitle, completedLessonCount, focusMinutes=45, navigationPath?`。当前工程若为 `courseId, courseTitle, navigationPath` 三参,需在调用处补传 `completedLessonCount`(如 `UserManager.shared.studyStats.lessons`)。 | -| **VerticalScreenPlayerView** | 若已按此前约定传 `CompletionView(courseId:courseTitle:completedLessonCount:navigationPath:)`,则参数兼容;**无需改其他页面文件**。 | -| **GrowthView / ProfileView / DiscoveryView** | 其 `navigationDestination(for: .completion)` 仍为 `CompletionView(courseId:courseTitle:completedLessonCount:)`;若 Gemini 版增加可选 `navigationPath`,调用处可不传或传入对应 path,**其他页面逻辑与展示不受影响**。 | - -结论:**仅替换 CompletionView 时,其他页面无需改代码即可编译**;但完结页**自身行为**会变(见上表)。 - ---- - -## 三、是否影响其他页面的逻辑与展示 - -| 维度 | 结论 | -|------|------| -| **其他页面逻辑** | 不受影响。仅 CompletionView 内部实现与状态变化。 | -| **其他页面展示** | 不受影响。无改动其他 View 或导航结构。 | -| **完结页自身逻辑与展示** | **会变**:从「勋章 + 点击点亮 + 回到我的内容 → 技能 Tab」变为「拍立得未显影 → 上传数据 → 显影 + 专注时长 + 回到我的内容 → 清栈/dismiss」,且**不切技能 Tab**。 | - -因此:**其他页面逻辑和展示不受影响**;**完结页的交互与目标(回到技能 Tab)会受影响**,需按需求修正。 - ---- - -## 四、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **其他页面** | 逻辑与展示均不受影响;仅替换 CompletionView 时调用方可不改或仅补参。 | -| **底部按钮** | 未调用 `navStore.switchToGrowthTab()`,不符合「回到技能 Tab-我的课程列表」需求,需补回。 | -| **数据与交互** | 写死 focusMinutes、上传模拟、hasUploadedData 与当前「仅展示接口/本地统计 + 统一分页」不一致;若采用本版,需与产品/接口对齐并接入真实数据。 | -| **建议** | 不应用本版;若保留「赛博拍立得」视觉,需在不动其他页面的前提下:① 底部按钮改为 `navStore.switchToGrowthTab()`;② 移除或对接「上传学习数据」与「专注时长」逻辑,与现有统计与导航一致。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/COMPLETION_迭代_完整代码.md b/ios/WildGrowth/COMPLETION_迭代_完整代码.md deleted file mode 100644 index 99fc8f3..0000000 --- a/ios/WildGrowth/COMPLETION_迭代_完整代码.md +++ /dev/null @@ -1,288 +0,0 @@ -# 完成页迭代 — 完整代码(仅交付,禁止自动应用) - -以下为审查修正后的完整代码。**关键修正**:底部「回到我的内容」改为 `navStore.switchToGrowthTab()`,不再使用 `navigationPath = NavigationPath()`,以保证从任意 Tab 进入都能回到技能页 Tab。 - ---- - -## 1. CompletionView.swift(整文件替换) - -```swift -import SwiftUI - -// MARK: - 🏆 课程完结页 (Gesture Flow Edition) -// 交互:左滑进入(上级控制),右滑返回(系统原生),底部点击回技能页 Tab -struct CompletionView: View { - let courseId: String - let courseTitle: String? - let completedLessonCount: Int - - @EnvironmentObject private var navStore: NavigationStore - @Environment(\.dismiss) private var dismiss - - @State private var isSealed = false - @State private var breathingOpacity: Double = 0.3 - @State private var contentOpacity: Double = 0 - - var body: some View { - ZStack { - Color.bgPaper.ignoresSafeArea() - - VStack(spacing: 0) { - Spacer().frame(height: 60) - - Spacer() - - ZStack { - Circle() - .strokeBorder( - isSealed ? - AnyShapeStyle( - LinearGradient( - colors: [Color.brandVital, Color.cyberIris], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) : - AnyShapeStyle(Color.inkSecondary.opacity(0.3)), - style: StrokeStyle(lineWidth: isSealed ? 4 : 1, dash: isSealed ? [] : [5, 5]) - ) - .frame(width: 220, height: 220) - .shadow( - color: isSealed ? Color.brandVital.opacity(0.4) : .clear, - radius: 20, x: 0, y: 0 - ) - .scaleEffect(isSealed ? 1.0 : 0.95) - .opacity(isSealed ? 1.0 : breathingOpacity) - .animation(.easeInOut(duration: 0.5), value: isSealed) - - if isSealed { - VStack(spacing: 8) { - Text("已完成的") - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.inkSecondary) - - Text("第 \(completedLessonCount) 节") - .font(.system(size: 36, weight: .bold, design: .serif)) - .foregroundStyle( - LinearGradient( - colors: [Color.brandVital, Color.cyberIris], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - } - .transition(.scale(scale: 0.5).combined(with: .opacity)) - } else { - Text("完") - .font(.system(size: 80, weight: .light, design: .serif)) - .foregroundColor(.inkPrimary.opacity(0.2)) - .opacity(breathingOpacity) - } - } - .contentShape(Circle()) - .onTapGesture { - triggerInteraction() - } - - Spacer() - - Button { - handleReturnToRoot() - } label: { - HStack(spacing: 4) { - Text("回到我的内容") - .font(.system(size: 15, weight: .medium)) - Image(systemName: "arrow.right") - .font(.system(size: 12)) - } - .foregroundColor(.brandVital) - .padding(.vertical, 12) - .padding(.horizontal, 32) - .background(Capsule().fill(Color.brandVital.opacity(0.05))) - } - .opacity(contentOpacity) - .padding(.bottom, 80) - } - } - .toolbar(.hidden, for: .navigationBar) - .modifier(SwipeBackEnablerModifier()) - .onAppear { - startBreathing() - } - } - - private func startBreathing() { - withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { - breathingOpacity = 0.8 - } - } - - private func triggerInteraction() { - guard !isSealed else { return } - let generator = UIImpactFeedbackGenerator(style: .heavy) - generator.impactOccurred() - withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) { - isSealed = true - } - withAnimation(.easeOut.delay(0.5)) { - contentOpacity = 1.0 - } - } - - /// 回到技能页 Tab 根目录(与当前「继续学习」一致) - private func handleReturnToRoot() { - navStore.switchToGrowthTab() - } -} - -// MARK: - 侧滑返回:隐藏导航栏时仍可右滑 pop -struct SwipeBackEnablerModifier: ViewModifier { - func body(content: Content) -> some View { - content.background(SwipeBackEnabler()) - } -} - -private struct SwipeBackEnabler: UIViewControllerRepresentable { - func makeUIViewController(context: Context) -> UIViewController { UIViewController() } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - DispatchQueue.main.async { - if let nc = uiViewController.navigationController { - nc.interactivePopGestureRecognizer?.delegate = nil - nc.interactivePopGestureRecognizer?.isEnabled = true - } - } - } -} -``` - ---- - -## 2. VerticalScreenPlayerView.swift(仅改动部分) - -### 2.1 保持现有 init 与属性 - -- `navigationPath: Binding?` 保持可选,NoteTreeView / NoteListView 不传时仍不 push 完成页。 -- `isLastNode`, `courseTitle` 等保持不变。 - -### 2.2 替换「加载成功」分支:去掉占位页,仅用左滑手势 push - -**原逻辑**(约 146–186 行): -`TabView` 内 `ForEach(allCourseNodes)` + `CompletionPlaceholderPage().tag("wg://completion")`,外加 `.onChange(of: currentNodeId)` 在 `newId == "wg://completion"` 时 append completion。 - -**新逻辑**: -- `TabView` 内**仅** `ForEach(allCourseNodes)`,每页在最后一节上挂 `LastPageSwipeModifier`,左滑时调用 `triggerCompletionNavigation`。 -- **删除** `CompletionPlaceholderPage` 及其 tag、**删除** `.onChange(of: currentNodeId)` 中与 `"wg://completion"` 相关的逻辑。 -- 新增 `@State private var isNavigatingToCompletion = false`(防抖),以及 `triggerCompletionNavigation()`、`LastPageSwipeModifier`。 - -**替换后的内容区代码**(直接替换原 `else if !allCourseNodes.isEmpty { ... }` 整块): - -```swift -} else if !allCourseNodes.isEmpty { - TabView(selection: $currentNodeId) { - ForEach(allCourseNodes, id: \.id) { node in - let isFirst = isFirstNodeInChapter(nodeId: node.id) - let chapterTitle = getChapterTitle(for: node.id) - - LessonPageView( - courseId: courseId, - nodeId: node.id, - currentGlobalNodeId: $currentNodeId, - initialScrollIndex: node.id == initialNodeId ? initialScrollIndex : nil, - headerConfig: HeaderConfig( - showChapterTitle: isFirst, - chapterTitle: chapterTitle - ), - courseTitle: courseTitle, - navigationPath: navigationPath - ) - .tag(node.id) - .modifier(LastPageSwipeModifier( - isLastPage: node.id == allCourseNodes.last?.id, - onSwipeLeft: triggerCompletionNavigation - )) - } - } - .tabViewStyle(.page(indexDisplayMode: .never)) - .ignoresSafeArea(.container, edges: .bottom) -} -``` - -### 2.3 新增状态与函数(放在 Logic Helpers 区域) - -```swift -@State private var isNavigatingToCompletion = false - -private func triggerCompletionNavigation() { - guard !isNavigatingToCompletion, let path = navigationPath else { return } - isNavigatingToCompletion = true - - let generator = UIImpactFeedbackGenerator(style: .medium) - generator.impactOccurred() - - let count = UserManager.shared.studyStats.lessons - path.wrappedValue.append(CourseNavigation.completion( - courseId: courseId, - courseTitle: courseTitle, - completedLessonCount: count - )) - - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - isNavigatingToCompletion = false - } -} -``` - -### 2.4 删除 onAppear 中「从完成页返回切回最后一节」逻辑 - -**删除**: - -```swift -if currentNodeId == "wg://completion", let lastId = allCourseNodes.last?.id { - currentNodeId = lastId -} -``` - -(去掉占位页后不再存在 `"wg://completion"` 选中的情况。) - -### 2.5 删除 CompletionPlaceholderPage,新增 LastPageSwipeModifier - -**删除** `CompletionPlaceholderPage` 结构体。 - -**在** `// MARK: - 📄 单页课程视图` **之前** 新增: - -```swift -// MARK: - 最后一页左滑:仅在最后一节挂载,左滑 push 完成页 -private struct LastPageSwipeModifier: ViewModifier { - let isLastPage: Bool - let onSwipeLeft: () -> Void - - func body(content: Content) -> some View { - content - .simultaneousGesture( - DragGesture(minimumDistance: 20, coordinateSpace: .local) - .onEnded { value in - guard isLastPage else { return } - if value.translation.width < -60, - abs(value.translation.width) > abs(value.translation.height) { - onSwipeLeft() - } - } - ) - } -} -``` - ---- - -## 3. 调用方(GrowthView / ProfileView / DiscoveryView) - -**无需改动**。CompletionView 仍为三参:`courseId`, `courseTitle`, `completedLessonCount`,依赖 `@EnvironmentObject navStore`,底部用 `switchToGrowthTab()` 回到技能页 Tab。 - ---- - -## 4. 小结 - -- **CompletionView**:无顶部返回、无打字机;赛博印章交互;右滑依赖 `SwipeBackEnablerModifier`;底部「回到我的内容」= `navStore.switchToGrowthTab()`。 -- **VerticalScreenPlayerView**:去掉占位页与 `onChange` 完成页逻辑;最后一节左滑用 `LastPageSwipeModifier` + `triggerCompletionNavigation` push 完成页;保留可选 `navigationPath`,笔记流不变。 -- **不修改** GrowthView / ProfileView / DiscoveryView 的 `CompletionView(...)` 调用。 diff --git a/ios/WildGrowth/COMPLETION_迭代_审查报告.md b/ios/WildGrowth/COMPLETION_迭代_审查报告.md deleted file mode 100644 index 411559e..0000000 --- a/ios/WildGrowth/COMPLETION_迭代_审查报告.md +++ /dev/null @@ -1,84 +0,0 @@ -# 完成页迭代方案 — 审查报告(禁止应用) - -**审查对象**:左滑进完成页、右滑回最后一节;无顶部返回;无打字机金句;底部「回到我的内容」回到技能页 Tab。 -**结论**:只做审查与完整代码输出,不修改仓库文件。 - ---- - -## 一、需求与方案对照 - -| 需求 | 方案 | 审查结论 | -|------|------|----------| -| 不想要点开页面导航栏的返回按钮 | 移除顶部导航栏和返回按钮,仅顶部留白 | ✅ 一致 | -| 右滑回到最后一个小节 | `.enableSwipeBack()` + `SwipeBackEnabler` 强制开启侧滑返回 | ⚠️ 见下文「侧滑返回」 | -| 最后小节左滑进完成页,右滑回最后小节 | 播放器最后一节挂 `LastPageSwipeModifier` 左滑 push;完成页右滑 = 系统 pop | ✅ 一致 | -| 不要打字机金句 | 移除打字机区域 | ✅ 一致 | -| 底部「回到我的内容」回到技能页 Tab | 方案写的是 `navigationPath = NavigationPath()` | ❌ **逻辑错误**,见下 | - ---- - -## 二、关键问题:底部按钮语义 - -**需求**:底部「回到我的内容」要回到**技能页 Tab**(即技能 Tab 根:课程列表)。 - -**方案中的实现**:`handleReturnToRoot()` 里写的是 `navigationPath = NavigationPath()`,即清空**当前传入的 path**。 - -- 若从**技能 Tab**进入:传的是 `growthPath`,清空后 = 技能 Tab 根 ✅ -- 若从**发现 Tab**进入:传的是 `homePath`,清空后 = **发现 Tab 根**,不会切到技能 Tab ❌ -- 若从**我的 Tab**进入:传的是 `profilePath`,清空后 = **我的 Tab 根**,不会切到技能 Tab ❌ - -因此:**仅清空当前 path 无法满足「无论从哪个 Tab 进,都回到技能页 Tab」**。 - -**正确做法**:底部按钮应调用 **`navStore.switchToGrowthTab()`**(切到技能 Tab + 清空 `growthPath`),与当前线上 CompletionView 的「继续学习」一致。 -- CompletionView 需保留 **`@EnvironmentObject var navStore`** -- 不需要为「回到我的内容」传入 **`@Binding var navigationPath`**(调用方无需改传参) - -**完整代码中已按此修正**:底部按钮调用 `navStore.switchToGrowthTab()`,不传 `navigationPath`。 - ---- - -## 三、侧滑返回(SwipeBackEnabler) - -- 方案用 **`SwipeBackEnabler`**(`UIViewControllerRepresentable`)在 `.background` 里查找 `navigationController` 并打开 `interactivePopGestureRecognizer`,以在隐藏导航栏时恢复右滑返回。 -- **风险**:`makeUIViewController` 返回的是一颗裸 `UIViewController()`,其 `navigationController` 在部分时机可能仍为 nil(例如尚未挂到 NavigationStack 上),`DispatchQueue.main.async` 能缓解但无法完全保证。若遇真机偶发无效,可考虑: - - 在 `updateUIViewController` 中轮询/延迟再取一次 `navigationController`,或 - - 使用 `UINavigationController` 子类 / 注入方式保证拿到的必为当前栈。 -- **结论**:实现可保留,建议在真机多场景(从三个 Tab 进入完成页后右滑)验证;若失效再加强时机或注入方式。 - ---- - -## 四、VerticalScreenPlayerView 与占位页 - -- 方案采用「**不使用占位页**,仅最后一节左滑手势 push 完成页」:TabView 只渲染 `ForEach(allCourseNodes)`,不再多一页 `CompletionPlaceholderPage`。 -- **影响**: - - 进入完成页**仅剩**「在最后一节左滑」这一种方式;「滑到下一空白页再进完成页」的路径被移除。 - - 从完成页右滑或 dismiss 后,播放器不再存在「当前是占位页、需在 onAppear 里切回最后一节」的状态,可删除 `onAppear` 里对 `currentNodeId == "wg://completion"` 的处理。 -- **与现有调用方**:NoteTreeView / NoteListView 不传 `navigationPath`,`triggerCompletionNavigation` 里需 `guard let path = navigationPath else { return }`,从笔记进的播放器仍不会 push 完成页,行为不变。 - ---- - -## 五、CompletionView 视觉与接口 - -- **视觉**:从当前 3D 翻转卡片 + 打字机,改为「赛博印章」:圆环 + 未盖章「完」/ 盖章后「已完成的第 N 节」+ 底部「回到我的内容」。 -- **接口**: - - 保留:`courseId`, `courseTitle`, `completedLessonCount`。 - - 不增加 `@Binding var navigationPath`(底部用 `navStore.switchToGrowthTab()`)。 - - 保留 `@EnvironmentObject var navStore`。 -- **调用方**:GrowthView / ProfileView / DiscoveryView 的 `.completion` 分支**无需**新增参数,仍为三参构造。 - ---- - -## 六、审查结论汇总 - -| 项 | 结论 | -|----|------| -| 顶部去掉返回按钮 | ✅ 方案正确 | -| 右滑回最后一节 | ✅ 思路正确;SwipeBackEnabler 需真机验证,偶发需加强时机 | -| 最后小节左滑进完成页 | ✅ 保留 LastPageSwipeModifier 即可 | -| 去掉打字机金句 | ✅ 方案正确 | -| 底部回到技能页 Tab | ❌ 方案用「清空当前 path」会错;应用 `navStore.switchToGrowthTab()`,完整代码已改 | -| 调用方改动 | ✅ CompletionView 不需新参数;VerticalScreenPlayerView 保持可选 `navigationPath`,笔记流不受影响 | - ---- - -**完整代码见下(仅作交付,不写入仓库)。** diff --git a/ios/WildGrowth/COMPLETION_重复修改审查.md b/ios/WildGrowth/COMPLETION_重复修改审查.md deleted file mode 100644 index d705b1e..0000000 --- a/ios/WildGrowth/COMPLETION_重复修改审查.md +++ /dev/null @@ -1,43 +0,0 @@ -# 主题色 & 思考流 — 重复修改审查 - -## 1. 主题色 (theme_color) - -| 位置 | 作用 | 是否重复 | -|------|------|----------| -| **backend/src/controllers/courseController.ts** | 管理后台创建课程后,`create` 不含 themeColor,随后 `update` 写入 `generateThemeColor(course.id)` | 同一流程内只写一次,无重复 | -| **backend/src/services/taskService.ts** | 用户/AI 创建课程后,同样 create 再 update 写入 theme_color | 与 courseController 是**不同入口**(后台 vs 用户),不是同一逻辑改两遍 | - -**结论:主题色没有“改重复”。** 两处对应两种创建路径,各自只设置一次。 - ---- - -## 2. 思考流 (thinking flow) - -### 2.1 文案重复(同一份长文案存在两处) - -| 位置 | 内容 | -|------|------| -| **backend/src/controllers/aiCourseController.ts** | `getThinkingFlow` 里返回的长文案(多段) | -| **ios/.../AICourseModels.swift** | `AICourseConstants.defaultThinkingFlowText`(多段) | - -两处文案内容一致。当前前端只用本地常量,不再请求接口,但**后端仍保留同一份文案**。以后若改文案需要改两处,属于重复维护。 - -### 2.2 前端未使用的代码(相对“改重复”的冗余) - -| 位置 | 说明 | -|------|------| -| **AICourseService.getThinkingFlow()** | 仍调用 `/api/ai/thinking-flow` 并带 5 分钟缓存,但 **CreationHubView 已不调用**,直接用 `AICourseConstants.defaultThinkingFlowText` | -| **CreateCourseInputView** | 已标记废弃,`thinkingFlowText` 初始为 `""`,未发现调用 `getThinkingFlow` | - -即:思考流接口和缓存逻辑还在,但当前创建流程已不用,相当于死代码。 - ---- - -## 3. 总结 - -- **主题色**:没有在同一流程里重复设置;courseController 与 taskService 是不同入口,逻辑未改重复。 -- **思考流**: - - 有**文案重复**(后端 + iOS 各一份相同长文案)。 - - 有**前端冗余**:`getThinkingFlow` 与缓存未被 CreationHubView 使用。 - -若需要,我可以**只在前端**做收敛(例如删除或标注不再使用 getThinkingFlow 的调用、在注释中说明文案以后只维护 AICourseConstants 一处),**不改后端**。 diff --git a/ios/WildGrowth/COMPLETION_错位印刷_CardFrontView_审查报告.md b/ios/WildGrowth/COMPLETION_错位印刷_CardFrontView_审查报告.md deleted file mode 100644 index ec9cc07..0000000 --- a/ios/WildGrowth/COMPLETION_错位印刷_CardFrontView_审查报告.md +++ /dev/null @@ -1,86 +0,0 @@ -# CardFrontView 错位印刷版 + CompletionView 微调 — 审查报告(禁止应用) - -**审查对象**:① CardFrontView 重构为「错位印刷风格」(Chromatic Aberration + 极粗宋体 + 紧凑叠字 + 胶囊标签);② CompletionView 主视图中漫射光晕透明度与卡片尺寸微调。 -**结论**:仅审查,**不应用、不修改**仓库内任何文件。 - ---- - -## 一、变更范围概览 - -| 变更项 | 当前实现 | 交付代码 | 说明 | -|--------|----------|----------|------| -| **CardFrontView** | 竖排大字 180pt bold,spacing -40,offset ±20,底部「点击开启」纯文字 + 呼吸动画 | 错位叠印(墨底 + 主层 blendMode)、220pt black,spacing -65,offset ±35/±10,底部白色胶囊「点击开启」固定 opacity 0.8 | 视觉风格从「艺术字」升级为「新中式赛博印刷风」 | -| **CompletionView 光晕** | 两枚 Circle 透明度 0.06 / 0.05 | 0.04 / 0.04 | 卡片正面白底更显眼 | -| **CompletionView 卡片容器** | .frame(width: 280, height: 400),cornerRadius 24 | .frame(width: 300, height: 440),cornerRadius 24 | 竖版藏书票感、视觉冲击更强 | - ---- - -## 二、CardFrontView 交付代码审查 - -### 2.1 设计逻辑与实现对应 - -| 设计点 | 实现方式 | 结论 | -|--------|----------|------| -| **错位叠印 (Chromatic Aberration)** | 同一 TypographyBody 画两遍:① 深紫灰墨底 offset(4,4) + blur(2);② 渐变主层 `.blendMode(.sourceAtop)` | ✅ 套色偏差感明确,有「印在纸上」的厚度 | -| **极粗宋体 (weight .black)** | 「完」「成」`.font(.system(size: 220, weight: .black, design: .serif))` | ✅ 笔画张力强,与「破纸而出」描述一致 | -| **紧凑排版 (spacing: -65)** | `VStack(spacing: -65)`,二字重叠 | ✅ 图形化图腾感,弱化可读、强化象征 | -| **标签胶囊** | 底部 HStack 包在 `Capsule().fill(Color.white).shadow(...)` 内,padding bottom 32,整体 opacity 0.8 | ✅ 不抢主视觉,又比纯文字更精致 | - -### 2.2 与当前 CardFrontView 的差异 - -- **当前**:单层渐变字 + `drawingGroup()`,底部为「arrow.up + 点击开启」纯文字 + `hintOpacity` 呼吸动画。 -- **交付**:双层(墨底 + 渐变主层)、无 `drawingGroup()`,底部为胶囊包裹的「点击开启」、无动画。 -- **命名**:交付代码仍为 `struct CardFrontView`,与当前一致,替换后 CompletionView 内 `CardFrontView(gradient: etherealGradient)` 无需改动。 - -### 2.3 需注意的点 - -| 项目 | 说明 | -|------|------| -| **shimmerOffset** | 交付代码中 `@State private var shimmerOffset: CGFloat = -0.5` 未在 body 内使用,属冗余状态,应用时可删除以免误导。 | -| **GeometryReader 与裁切** | 文字层用 `GeometryReader` + `.position(center)` 居中,外层 `.clipShape(RoundedRectangle(cornerRadius: 24))` 与当前一致,裁切行为正常。 | -| **噪点层** | `Color.gray.opacity(0.03)` 作为极淡纸张噪点,对性能影响可忽略。 | - ---- - -## 三、CompletionView 主视图微调审查 - -交付说明中的两处修改与当前代码的对应关系如下。 - -### 3.1 漫射光晕透明度 - -- **当前**: - `Color(red: 1.0, green: 0.45, blue: 0.85).opacity(0.06)` - `Color(red: 0.58, green: 0.28, blue: 0.95).opacity(0.05)` -- **交付**: - 两处均改为 `.opacity(0.04)`。 - -效果:背景光晕更弱,卡片正面白底更纯粹,与「让卡片正面的白更显眼」一致。 - -### 3.2 卡片容器尺寸与圆角 - -- **当前**: - `.frame(width: 280, height: 400)`,`.cornerRadius(24)` 已在父级或同层使用。 -- **交付**: - `.frame(width: 300, height: 440)`,圆角保持 24。 - -应用时需在 **CompletionView** 的 body 中,将包裹 `CardBackView` / `CardFrontView` 的那层 ZStack 的 `.frame(width: 280, height: 400)` 改为 `.frame(width: 300, height: 440)`;若该层未写 cornerRadius,保持现有 `.cornerRadius(24)` 即可。 - ---- - -## 四、接口与调用方影响 - -- **CardFrontView**:仍为 `CardFrontView(gradient: LinearGradient)`,仅内部实现与视觉变化,CompletionView 调用处无需改。 -- **CompletionView**:仅 body 内光晕透明度与卡片容器尺寸变化,入参、导航、按钮逻辑均不变,VerticalScreenPlayerView 等调用方无需改。 - ---- - -## 五、审查结论汇总 - -| 项目 | 结论 | -|------|------| -| **CardFrontView 错位印刷版** | 设计逻辑清晰,墨底 + sourceAtop、220pt black、spacing -65、胶囊标签均与「新中式赛博印刷风」描述一致;仅 `shimmerOffset` 未使用可删。 | -| **CompletionView 光晕** | 0.06/0.05 → 0.04/0.04 合理,正面白更突出。 | -| **CompletionView 卡片尺寸** | 280×400 → 300×440 与「竖版藏书票」描述一致。 | -| **接口与其它页面** | 无新增参数、无改动导航或 Tab,仅完结页内部视觉与尺寸调整。 | - -**未对仓库内任何文件进行修改。** diff --git a/ios/WildGrowth/SF_SYMBOL_使用清单.md b/ios/WildGrowth/SF_SYMBOL_使用清单.md deleted file mode 100644 index 0e25359..0000000 --- a/ios/WildGrowth/SF_SYMBOL_使用清单.md +++ /dev/null @@ -1,259 +0,0 @@ -# SF Symbol 使用清单 - -项目中所有使用 SF Symbol 的位置及对应 symbol 名称。 - ---- - -## 一、按文件列出 - -### VerticalScreenPlayerView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 56 | `arrow.left` | 返回按钮 | -| 154 | `exclamationmark.triangle` | 错误/警告提示 | -| 209 | `book.closed` | 课程/书本占位 | -| 814 | `exclamationmark.triangle` | 错误态占位 | - -### MapView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 84 | `exclamationmark.triangle` | 错误提示 | -| 344 | `arrow.left` | 返回按钮 | -| 409 | `iconName`(动态) | 地图头水印,来自 `data.watermarkIcon` | -| 415 | `book.closed.fill` | 地图头水印兜底(无 watermarkIcon 时) | -| 597 | `lock.fill` | 节点锁定状态 | -| 607 | `checkmark` | 节点已完成 | -| 618 | `play.fill` | 节点可播放 | - -### LoginView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 51 | `xmark` | 关闭弹窗 | -| 106 | `checkmark.square.fill` / `square` | 同意协议勾选(已选/未选) | -| 176 | `applelogo` | 苹果登录按钮 | -| 364 | `arrow.left` | 返回按钮 | - -### ProfileView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 285 | `lock.circle.fill` | 锁定课程标识 | -| 393 | `course.watermarkIconName`(动态) | 课程卡片水印 | -| 421 | `course.watermarkIconName`(动态) | 课程卡片水印 | -| 530 | `exclamationmark.triangle.fill` | 错误提示 | -| 629 | `chevron.right` | 右箭头/进入 | -| 738 | `book.closed` | 空状态/占位 | -| 814 | `xmark.circle.fill` | 关闭/删除 | -| 713 | `pencil` | Label「编辑信息」 | -| 720 | `trash` | Label「删除」 | - -### Views/Profile/CyberLearningIDCard.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 174 | `square.and.pencil` | 编辑 | -| 196 | `person.fill` | 头像占位 | - -### NoteBottomSheetView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 52 | `quote.opening` | 引用/摘录 | -| 95 | `trash` | 删除 | -| 211 | `ellipsis` | 更多菜单 | - -### Views/GrowthView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 71 | `book.closed` | 课程占位 | -| 208 | `lock.circle.fill` | 锁定课程 | -| 578 | `exclamationmark.circle.fill` / `checkmark.circle.fill` | Toast 类型图标 | -| 628 | `trash` | Label「移除课程」 | -| 635 | `doc.text` | Label「查看详情」 | -| 647 | `pencil` | Label「修改课程名称」 | -| 656 | `trash` | Label「移除课程」 | - -### Views/Growth/GrowthTopBar.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 22 | `plus` | 添加按钮 | - -### ArticleRichTextView.swift(UIKit 菜单) -| 行号 | Symbol | 用途 | -|------|--------|------| -| 740 | `highlighter` | 划线 | -| 745 | `bubble.left.and.bubble.right` | 写想法 | -| 750 | `doc.on.doc` | 复制 | - -### Views/DiscoveryComponents.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 63 | `iconName`(动态) | 发现流卡片水印,来自 `item.watermarkIcon` | -| 145 | `iconName`(动态) | 信息流卡片水印,来自 `course.watermarkIcon` | - -### Views/CreationHubView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 86 | `arrow.up.circle.fill` | 上传/发布 | -| 194 | `checkmark.circle.fill` | 成功状态 | -| 219 | `xmark.circle.fill` | 失败/关闭 | -| 460 | `icon`(动态) | 列表行图标,见下方取值 | -| 473 | `chevron.right` | 右箭头 | -| 557 | `book.closed` | 空状态 | - -**CreationHubRow 的 `icon` 取值(写死在调用处):** -- `folder.fill` — 我的创作 -- `doc.text.fill` — 从文档创建 -- `arrow.triangle.2.circlepath` — 从已有课程复制 - -### DesignSystem.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 337 | `photo` | 占位图/无图 | - -### Views/PersonaSelectionView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 102 | `chevron.left` | 返回 | -| 172 | `checkmark.circle.fill` | 已选 | -| 177 | `circle` | 未选 | - -### Views/CreateCourseInputView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 158 | `doc.fill` | 文档 | -| 180 | `doc.fill` | 文档 | -| 199 | `xmark.circle.fill` | 关闭/清除 | - -### Views/GenerationProgressView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 84 | `exclamationmark.triangle.fill` | 错误 | -| 163 | `checkmark.circle.fill` | 成功 | -| 198 | `xmark` | 关闭窗口 | - -### NotebookListView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 117 | `lock.doc.fill` | 锁定笔记本 | -| 221 | `book.closed` | 空状态 | -| 193 | `pencil` | Label「编辑信息」 | -| 200 | `trash` | Label「删除」 | - -### Views/Profile/ProfileNoteListView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 16 | `note.text` | 笔记列表 | -| 113 | `book.closed` | 空状态 | - -### Views/Profile/EditProfileSheet.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 52 | `person.fill` | 头像占位 | -| 65 | `person.fill` | 头像占位 | -| 77 | `camera.fill` | 拍照/相册 | - -### Views/Profile/AvatarPicker.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 14 | `photo.stack` | 相册选择 | - -### PaywallView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 31 | `xmark.circle.fill` | 关闭 | -| 45 | `lock.open.fill` | 解锁/付费入口 | -| 133 | `checkmark.circle.fill` | 权益项勾选 | - -### ToastView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 23 | `exclamationmark.circle.fill` / `checkmark.circle.fill` / `info.circle.fill` | 按 Toast 类型(error/success/info) | - -### NoteTreeView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 136 | `square.and.pencil` | 编辑 | - -### NoteListView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 73 | `note.text` | 笔记列表 | -| 218 | `book.closed.fill` | 空状态 | -| 257 | `highlighter` | 划线 | -| 307 | `note.text.badge.plus` | 新建笔记 | - -### NoteInputView.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 35 | `quote.opening` | 引用样式 | - -### Views/NoteTree/NoteTreeRow.swift -| 行号 | Symbol | 用途 | -|------|--------|------| -| 72 | `pencil` | Label「编辑」 | -| 76 | `trash` | Label「删除」 | - ---- - -## 二、动态 Symbol 来源汇总 - -| 使用处 | 变量/属性 | 可能取值(来源) | -|--------|-----------|------------------| -| MapView 地图头水印 | `data.watermarkIcon` | 后端 API;无则兜底 `book.closed.fill` | -| DiscoveryComponents 卡片水印 | `item.watermarkIcon` / `course.watermarkIcon` | 后端 API | -| ProfileView 课程水印 | `course.watermarkIconName` | 见 CourseModels 逻辑 | - -**CourseModels.watermarkIconName 逻辑:** -- 若有 `watermarkIcon` 且非空 → 用该值 -- 否则按 `type`:`system` → `book.closed.fill`,`single` → `doc.text.fill`,`vertical_screen` → `rectangle.portrait.fill`,默认 → `doc.text.fill` - ---- - -## 三、按 Symbol 名称汇总(便于替换/统一) - -| Symbol | 出现位置(简要) | -|--------|------------------| -| `arrow.left` | VerticalScreenPlayerView, MapView, LoginView 返回 | -| `arrow.up.circle.fill` | CreationHubView 上传 | -| `arrow.triangle.2.circlepath` | CreationHubView 从课程复制 | -| `applelogo` | LoginView 苹果登录 | -| `book.closed` | VerticalScreenPlayerView, ProfileView, GrowthView, CreationHubView, NotebookListView, ProfileNoteListView 占位/空态 | -| `book.closed.fill` | MapView 水印兜底;CourseModels 中 type=system;NoteListView 空态 | -| `bubble.left.and.bubble.right` | ArticleRichTextView 写想法 | -| `camera.fill` | EditProfileSheet | -| `checkmark` | MapView 节点完成 | -| `checkmark.circle.fill` | 多处成功/已选/权益勾选 | -| `checkmark.square.fill` / `square` | LoginView 协议勾选 | -| `chevron.left` | PersonaSelectionView 返回 | -| `chevron.right` | ProfileView, CreationHubView 进入下一级 | -| `circle` | PersonaSelectionView 未选 | -| `doc.fill` | CreateCourseInputView | -| `doc.on.doc` | ArticleRichTextView 复制 | -| `doc.text` | GrowthView Label 查看详情 | -| `doc.text.fill` | CreationHubView 从文档创建;CourseModels single/默认 | -| `ellipsis` | NoteBottomSheetView 更多 | -| `exclamationmark.circle.fill` | GrowthView/ToastView 错误 | -| `exclamationmark.triangle` | VerticalScreenPlayerView, MapView 警告 | -| `exclamationmark.triangle.fill` | ProfileView, GenerationProgressView 错误 | -| `folder.fill` | CreationHubView 我的创作 | -| `highlighter` | ArticleRichTextView 划线;NoteListView 划线 | -| `info.circle.fill` | ToastView info 类型 | -| `lock.fill` | MapView 节点锁定 | -| `lock.circle.fill` | ProfileView, GrowthView 课程锁定 | -| `lock.doc.fill` | NotebookListView 笔记本锁定 | -| `lock.open.fill` | PaywallView 解锁 | -| `note.text` | ProfileNoteListView, NoteListView | -| `note.text.badge.plus` | NoteListView 新建笔记 | -| `pencil` | ProfileView, GrowthView, NotebookListView, NoteTreeRow Label 编辑 | -| `person.fill` | CyberLearningIDCard, EditProfileSheet | -| `photo` | DesignSystem 占位 | -| `photo.stack` | AvatarPicker | -| `play.fill` | MapView 节点播放 | -| `quote.opening` | NoteBottomSheetView, NoteInputView 引用 | -| `rectangle.portrait.fill` | CourseModels vertical_screen 水印 | -| `square.and.pencil` | CyberLearningIDCard, NoteTreeView 编辑 | -| `trash` | 多处 Label/按钮 删除 | -| `xmark` | LoginView, GenerationProgressView 关闭 | -| `xmark.circle.fill` | ProfileView, CreationHubView, CreateCourseInputView, PaywallView 关闭/清除 | - ---- - -*文档根据当前代码静态分析生成,若新增或修改 SF Symbol 请同步更新此清单。* diff --git a/ios/WildGrowth/WildGrowth/APIClient.swift b/ios/WildGrowth/WildGrowth/APIClient.swift index 1c56008..cc5ed37 100644 --- a/ios/WildGrowth/WildGrowth/APIClient.swift +++ b/ios/WildGrowth/WildGrowth/APIClient.swift @@ -3,37 +3,13 @@ import Foundation class APIClient { static let shared = APIClient() - #if DEBUG - // MARK: - 开发环境配置 (Debug) - + // MARK: - 环境配置 (由 xcconfig 的 API_DOMAIN 注入 Info.plist) + // 在 Xcode 中切换 Build Configuration 即可切换环境 + var baseURL: String { - // 【⚠️ 生产环境调试开关】 - // true = Debug 模式下连接线上服务器 (用于上线前验证 HTTPS 和数据) - // false = Debug 模式下连接本地 localhost (用于日常开发) - let useProduction = false // <--- 当前已开启,方便你马上测试! - - if useProduction { - return "https://api.muststudy.xin" - } - - #if targetEnvironment(simulator) - // 模拟器:访问电脑本机 - return "http://localhost:3000" - #else - // 真机调试 (连接本地后端): - // ⚠️ 如果切换回 false,请确保这里是你电脑的实际局域网 IP - return "http://192.168.1.100:3000" - #endif + (Bundle.main.infoDictionary?["API_DOMAIN"] as? String) ?? "https://api.muststudy.xin" } - #else - // MARK: - 生产环境配置 (Release/TestFlight) - - /// 生产环境固定使用线上域名,打包 / TestFlight 必走此处 - var baseURL: String { "https://api.muststudy.xin" } - - #endif - // ✅ 复用 URLSession,避免每次请求都创建新的 session private let session: URLSession diff --git a/ios/WildGrowth/WildGrowth/WildGrowthApp.swift b/ios/WildGrowth/WildGrowth/WildGrowthApp.swift index c719cbc..a97145f 100644 --- a/ios/WildGrowth/WildGrowth/WildGrowthApp.swift +++ b/ios/WildGrowth/WildGrowth/WildGrowthApp.swift @@ -7,6 +7,14 @@ struct WildGrowthApp: App { @StateObject private var userManager = UserManager.shared init() { + // 🌐 启动时打印 API 环境配置(便于排查 xcconfig 注入是否生效) + let rawAPI = Bundle.main.infoDictionary?["API_DOMAIN"] as? String + let baseURL = APIClient.shared.baseURL + print("🌐 [API] Info.plist API_DOMAIN = \(rawAPI ?? "nil")") + print("🌐 [API] APIClient.baseURL = \(baseURL)") + if rawAPI == nil { + print("⚠️ [API] API_DOMAIN 未从 xcconfig 注入,使用默认值。请检查 Build Configuration 和 INFOPLIST_KEY_API_DOMAIN") + } // 🔥 优化 Kingfisher 配置,提升图片加载速度 configureKingfisher() // ✅ V1.0 埋点:初始化 + 记录冷启动 diff --git a/ios/WildGrowth/字号优化说明.md b/ios/WildGrowth/字号优化说明.md deleted file mode 100644 index 75afd6d..0000000 --- a/ios/WildGrowth/字号优化说明.md +++ /dev/null @@ -1,94 +0,0 @@ -# 字号优化说明 - 2026年2月 - -## 📊 优化原则 - -**核心理念**:调整略小的字号至合理层级,保持已经合理的字号不变,确保 UI 一致性和良好的阅读体验。 - ---- - -## ✅ 已调整的字号(略小 → 合理) - -### 1. 播放器正文区域 (`ContentBlockBuilder.swift`) - -| 元素 | 原字号 | 新字号 | 调整理由 | -|------|--------|--------|---------| -| 正文 | 17pt | **19pt** | 提升长文阅读舒适度,符合阅读类应用标准 | -| 主标题 | 28pt | **30pt** | 与正文字号成比例协调 | -| H1 标题 | 24pt | **26pt** | 与正文字号成比例协调 | -| H2 标题 | 20pt | **22pt** | 与正文字号成比例协调 | -| 高亮文本 | 18pt | **20pt** | 与正文字号成比例协调 | -| 行间距 | 9pt | **10pt** | 保持黄金比例 1.72x (19×1.72≈32.7) | -| 段落间距 | 22pt | **24pt** | 随正文字号增大 | - -**影响范围**: -- ✅ `VerticalScreenPlayerView` - 播放器正文渲染 -- ✅ `ArticleRichTextViewRepresentable` - 富文本显示 - ---- - -### 2. 地图页 (`MapView.swift`) - -| 元素 | 原字号 | 新字号 | 调整理由 | -|------|--------|--------|---------| -| 章节标题 | 20pt | **22pt** | 增强区块分割感,提升层级清晰度 | -| 课程标题 | 16pt | **17pt** | 略微偏小,提升至标准阅读字号 | -| 进度信息 | caption (11-12pt) | **label (13pt)** | caption 过小,调整至 label 防止难以阅读 | - -**影响范围**: -- ✅ `VerticalListLayout` - 章节标题 -- ✅ `LessonListRow` - 课程条目标题 -- ✅ `AtmosphericHeaderView` - 悬浮卡片进度信息 - ---- - -## 🔒 保持不变的合理字号 - -以下字号已处于最佳层级,**无需调整**: - -| 区域 | 元素 | 字号 | 评价 | -|------|------|------|------| -| **MapView** | 头部大标题 | 32pt | ✅ 适当,具有冲击力 | -| **ProfileView** | 统计数字 | 32pt | ✅ 适当,数据展示清晰 | -| **DiscoveryView** | 运营位标题 | 22pt | ✅ 适当,信息层级合理 | -| **GrowthTopBar** | 导航标签 | 18pt (active) / 16pt (inactive) | ✅ 适当,选中态明显 | -| **DesignSystem** | 各类按钮与标签 | 13pt-16pt | ✅ 适当,符合 iOS 设计规范 | -| **ProfileView** | 笔记入口卡片 | 16pt (title) / 13pt (label) | ✅ 适当,层级清晰 | -| **MapView** | 悬浮卡片按钮 | 15pt | ✅ 适当,易于点击 | - ---- - -## 📈 字号层级体系(完整) - -| 层级 | 字号范围 | 用途 | 示例 | -|------|----------|------|------| -| **Display** | 30-32pt | 页面主标题、数据展示 | 播放器主标题 (30pt)、统计数字 (32pt) | -| **H1** | 26-28pt | 一级标题 | 播放器 H1 (26pt) | -| **H2** | 22-24pt | 二级标题、模块标题 | 地图章节标题 (22pt)、发现页标题 (22pt) | -| **H3** | 18-20pt | 三级标题、Tab 标签 | 播放器高亮 (20pt)、Tab Bar 选中 (18pt) | -| **Body** | 17-19pt | 正文、列表项 | 播放器正文 (19pt)、地图课程标题 (17pt) | -| **Label** | 13-16pt | 辅助信息、按钮 | 进度信息 (13pt)、按钮文字 (16pt) | -| **Caption** | 11-12pt | 极小辅助文本(谨慎使用) | ⚠️ 已基本避免使用 | - ---- - -## 🎯 设计原则总结 - -1. **阅读优先**:正文区域字号要足够大(19pt),确保长时间阅读不疲劳 -2. **比例协调**:标题与正文成比例缩放,保持视觉节奏 -3. **层级清晰**:字号差异明显(至少 2-4pt),确保信息层级清晰 -4. **避免过小**:尽量避免使用 caption(11-12pt),最小使用 label(13pt) -5. **符合规范**:参考 Apple HIG 和主流阅读应用(微信读书、即刻等) - ---- - -## 📝 后续建议 - -1. **播放器体验**:可考虑根据用户反馈,提供字号调节功能(小、中、大) -2. **无障碍支持**:确保所有文字支持动态字体(Dynamic Type) -3. **对比测试**:可通过 A/B 测试验证字号调整对阅读完成率的影响 -4. **一致性检查**:定期审查新增页面,确保字号符合设计系统 - ---- - -**优化完成日期**:2026年2月1日 -**Git 提交记录**:`2a44791` - 优化字号层级,提升阅读体验 diff --git a/ios/WildGrowth/电子成长.xcodeproj/project.pbxproj b/ios/WildGrowth/电子成长.xcodeproj/project.pbxproj index fbcd13b..e107f42 100644 --- a/ios/WildGrowth/电子成长.xcodeproj/project.pbxproj +++ b/ios/WildGrowth/电子成长.xcodeproj/project.pbxproj @@ -7,8 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - A328ABC72F01FCC20031E45F /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = A328ABC62F01FCC20031E45F /* Kingfisher */; }; - A38376772F1489CF0027969A /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = A38376762F1489CF0027969A /* MarkdownUI */; }; + 907A366B2F3DC0A200A4BA77 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = A328ABC62F01FCC20031E45F /* Kingfisher */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -32,6 +31,9 @@ A357EF6C2EE29B130004B865 /* WildGrowth.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WildGrowth.app; sourceTree = BUILT_PRODUCTS_DIR; }; A357EF7D2EE29B140004B865 /* WildGrowthTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WildGrowthTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A357EF872EE29B140004B865 /* WildGrowthUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WildGrowthUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A3CF00022EE29B130004B865 /* Online.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Online.xcconfig; sourceTree = ""; }; + A3CF00032EE29B130004B865 /* Develop.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Develop.xcconfig; sourceTree = ""; }; + A3CF00042EE29B130004B865 /* Local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Local.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -57,8 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A328ABC72F01FCC20031E45F /* Kingfisher in Frameworks */, - A38376772F1489CF0027969A /* MarkdownUI in Frameworks */, + 907A366B2F3DC0A200A4BA77 /* Kingfisher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +93,7 @@ A357EF6E2EE29B130004B865 /* WildGrowth */, A357EF802EE29B140004B865 /* WildGrowthTests */, A357EF8A2EE29B140004B865 /* WildGrowthUITests */, + A3CF00012EE29B130004B865 /* Config */, A328ABC52F01FCC20031E45F /* Frameworks */, A357EF6D2EE29B130004B865 /* Products */, ); @@ -107,6 +109,16 @@ name = Products; sourceTree = ""; }; + A3CF00012EE29B130004B865 /* Config */ = { + isa = PBXGroup; + children = ( + A3CF00022EE29B130004B865 /* Online.xcconfig */, + A3CF00032EE29B130004B865 /* Develop.xcconfig */, + A3CF00042EE29B130004B865 /* Local.xcconfig */, + ); + path = Config; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -129,7 +141,6 @@ name = WildGrowth; packageProductDependencies = ( A328ABC62F01FCC20031E45F /* Kingfisher */, - A38376762F1489CF0027969A /* MarkdownUI */, ); productName = WildGrowth; productReference = A357EF6C2EE29B130004B865 /* WildGrowth.app */; @@ -188,6 +199,9 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; + KnownAssetTags = ( + New, + ); LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1620; TargetAttributes = { @@ -214,8 +228,8 @@ mainGroup = A357EF632EE29B130004B865; minimizedProjectReferenceProxies = 1; packageReferences = ( - A328ABC22F01FA250031E45F /* XCLocalSwiftPackageReference "../../../../Downloads/Kingfisher-master" */, - A38376752F1489CF0027969A /* XCRemoteSwiftPackageReference "MarkdownUI" */, + 907A352B2F3CA3B000A4BA77 /* XCRemoteSwiftPackageReference "Kingfisher" */, + 907A352C2F3CA60C00A4BA77 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, ); preferredProjectObjectVersion = 77; productRefGroup = A357EF6D2EE29B130004B865 /* Products */; @@ -295,8 +309,202 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - A357EF8F2EE29B140004B865 /* Debug */ = { + A357EF922EE29B140004B865 /* Debug-Online */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = WildGrowth/WildGrowth.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; + DEVELOPMENT_TEAM = JMT5SK3SWY; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Config/Info-API.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowth; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Online"; + }; + A357EF932EE29B140004B865 /* Release-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = WildGrowth/WildGrowth.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; + DEVELOPMENT_TEAM = JMT5SK3SWY; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowth; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Online"; + }; + A357EF952EE29B140004B865 /* Debug-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Online"; + }; + A357EF962EE29B140004B865 /* Release-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Online"; + }; + A357EF982EE29B140004B865 /* Debug-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_TARGET_NAME = WildGrowth; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Online"; + }; + A357EF992EE29B140004B865 /* Release-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_TARGET_NAME = WildGrowth; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Online"; + }; + A3CF00112EE29B130004B865 /* Debug-Online */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -354,10 +562,11 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; - name = Debug; + name = "Debug-Online"; }; - A357EF902EE29B140004B865 /* Release */ = { + A3CF00122EE29B130004B865 /* Release-Online */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00022EE29B130004B865 /* Online.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -407,10 +616,243 @@ MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; - name = Release; + name = "Release-Online"; }; - A357EF922EE29B140004B865 /* Debug */ = { + A3CF00132EE29B130004B865 /* Debug-Develop */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = "Debug-Develop"; + }; + A3CF00142EE29B130004B865 /* Release-Develop */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = "Release-Develop"; + }; + A3CF00152EE29B130004B865 /* Debug-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = "Debug-Local"; + }; + A3CF00162EE29B130004B865 /* Release-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = "Release-Local"; + }; + A3CF00212EE29B130004B865 /* Debug-Develop */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -421,10 +863,11 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; - DEVELOPMENT_TEAM = TGTAAHD84B; + DEVELOPMENT_TEAM = JMT5SK3SWY; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Config/Info-API.plist"; INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; @@ -455,10 +898,11 @@ TARGETED_DEVICE_FAMILY = 1; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Debug; + name = "Debug-Develop"; }; - A357EF932EE29B140004B865 /* Release */ = { + A3CF00222EE29B130004B865 /* Release-Develop */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -469,10 +913,11 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; - DEVELOPMENT_TEAM = TGTAAHD84B; + DEVELOPMENT_TEAM = JMT5SK3SWY; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Config/Info-API.plist"; INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; @@ -503,10 +948,113 @@ TARGETED_DEVICE_FAMILY = 1; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Release; + name = "Release-Develop"; }; - A357EF952EE29B140004B865 /* Debug */ = { + A3CF00232EE29B130004B865 /* Debug-Local */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + API_DOMAIN = ""; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = WildGrowth/WildGrowth.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; + DEVELOPMENT_TEAM = JMT5SK3SWY; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Config/Info-API.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowth; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Local"; + }; + A3CF00242EE29B130004B865 /* Release-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + API_DOMAIN = ""; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = WildGrowth/WildGrowth.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WildGrowth/Preview Content\""; + DEVELOPMENT_TEAM = JMT5SK3SWY; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Config/Info-API.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "电子成长"; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books"; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowth; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Local"; + }; + A3CF00252EE29B130004B865 /* Debug-Develop */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -526,10 +1074,11 @@ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Debug; + name = "Debug-Develop"; }; - A357EF962EE29B140004B865 /* Release */ = { + A3CF00262EE29B130004B865 /* Release-Develop */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -549,10 +1098,59 @@ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Release; + name = "Release-Develop"; }; - A357EF982EE29B140004B865 /* Debug */ = { + A3CF00272EE29B130004B865 /* Debug-Local */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Local"; + }; + A3CF00282EE29B130004B865 /* Release-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WildGrowth.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/WildGrowth"; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Local"; + }; + A3CF00292EE29B130004B865 /* Debug-Develop */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -571,10 +1169,11 @@ TEST_TARGET_NAME = WildGrowth; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Debug; + name = "Debug-Develop"; }; - A357EF992EE29B140004B865 /* Release */ = { + A3CF002A2EE29B130004B865 /* Release-Develop */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00032EE29B130004B865 /* Develop.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -593,7 +1192,53 @@ TEST_TARGET_NAME = WildGrowth; XROS_DEPLOYMENT_TARGET = 2.2; }; - name = Release; + name = "Release-Develop"; + }; + A3CF002B2EE29B130004B865 /* Debug-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_TARGET_NAME = WildGrowth; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Debug-Local"; + }; + A3CF002C2EE29B130004B865 /* Release-Local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3CF00042EE29B130004B865 /* Local.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TGTAAHD84B; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mustmaster.WildGrowthUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_TARGET_NAME = WildGrowth; + XROS_DEPLOYMENT_TARGET = 2.2; + }; + name = "Release-Local"; }; /* End XCBuildConfiguration section */ @@ -601,52 +1246,69 @@ A357EF672EE29B130004B865 /* Build configuration list for PBXProject "电子成长" */ = { isa = XCConfigurationList; buildConfigurations = ( - A357EF8F2EE29B140004B865 /* Debug */, - A357EF902EE29B140004B865 /* Release */, + A3CF00112EE29B130004B865 /* Debug-Online */, + A3CF00122EE29B130004B865 /* Release-Online */, + A3CF00132EE29B130004B865 /* Debug-Develop */, + A3CF00142EE29B130004B865 /* Release-Develop */, + A3CF00152EE29B130004B865 /* Debug-Local */, + A3CF00162EE29B130004B865 /* Release-Local */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release-Online"; }; A357EF912EE29B140004B865 /* Build configuration list for PBXNativeTarget "WildGrowth" */ = { isa = XCConfigurationList; buildConfigurations = ( - A357EF922EE29B140004B865 /* Debug */, - A357EF932EE29B140004B865 /* Release */, + A357EF922EE29B140004B865 /* Debug-Online */, + A357EF932EE29B140004B865 /* Release-Online */, + A3CF00212EE29B130004B865 /* Debug-Develop */, + A3CF00222EE29B130004B865 /* Release-Develop */, + A3CF00232EE29B130004B865 /* Debug-Local */, + A3CF00242EE29B130004B865 /* Release-Local */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release-Online"; }; A357EF942EE29B140004B865 /* Build configuration list for PBXNativeTarget "WildGrowthTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - A357EF952EE29B140004B865 /* Debug */, - A357EF962EE29B140004B865 /* Release */, + A357EF952EE29B140004B865 /* Debug-Online */, + A357EF962EE29B140004B865 /* Release-Online */, + A3CF00252EE29B130004B865 /* Debug-Develop */, + A3CF00262EE29B130004B865 /* Release-Develop */, + A3CF00272EE29B130004B865 /* Debug-Local */, + A3CF00282EE29B130004B865 /* Release-Local */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release-Online"; }; A357EF972EE29B140004B865 /* Build configuration list for PBXNativeTarget "WildGrowthUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( - A357EF982EE29B140004B865 /* Debug */, - A357EF992EE29B140004B865 /* Release */, + A357EF982EE29B140004B865 /* Debug-Online */, + A357EF992EE29B140004B865 /* Release-Online */, + A3CF00292EE29B130004B865 /* Debug-Develop */, + A3CF002A2EE29B130004B865 /* Release-Develop */, + A3CF002B2EE29B130004B865 /* Debug-Local */, + A3CF002C2EE29B130004B865 /* Release-Local */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release-Online"; }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - A328ABC22F01FA250031E45F /* XCLocalSwiftPackageReference "../../../../Downloads/Kingfisher-master" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = "../../../../Downloads/Kingfisher-master"; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCRemoteSwiftPackageReference section */ - A38376752F1489CF0027969A /* XCRemoteSwiftPackageReference "MarkdownUI" */ = { + 907A352B2F3CA3B000A4BA77 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/gonzalezreal/MarkdownUI"; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.6.2; + }; + }; + 907A352C2F3CA60C00A4BA77 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.4.1; @@ -657,19 +1319,14 @@ /* Begin XCSwiftPackageProductDependency section */ A328ABC32F01FA6A0031E45F /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = A328ABC22F01FA250031E45F /* XCLocalSwiftPackageReference "../../../../Downloads/Kingfisher-master" */; + package = 907A352B2F3CA3B000A4BA77 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; A328ABC62F01FCC20031E45F /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = A328ABC22F01FA250031E45F /* XCLocalSwiftPackageReference "../../../../Downloads/Kingfisher-master" */; + package = 907A352B2F3CA3B000A4BA77 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; - A38376762F1489CF0027969A /* MarkdownUI */ = { - isa = XCSwiftPackageProductDependency; - package = A38376752F1489CF0027969A /* XCRemoteSwiftPackageReference "MarkdownUI" */; - productName = MarkdownUI; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = A357EF642EE29B130004B865 /* Project object */; diff --git a/ios/WildGrowth/电子成长.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/WildGrowth/电子成长.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e573ac1..6c89395 100644 --- a/ios/WildGrowth/电子成长.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/WildGrowth/电子成长.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "f430e49d6b841dc65fa45490167bf3212b889ddf3e49a795fe473a7f0c2af4ac", + "originHash" : "18350c2bfa3935125b6f4e9817e7ed4508588c07142d420b8b8ee00640a57853", "pins" : [ { - "identity" : "markdownui", + "identity" : "kingfisher", "kind" : "remoteSourceControl", - "location" : "https://github.com/gonzalezreal/MarkdownUI", + "location" : "https://github.com/onevcat/Kingfisher", "state" : { - "revision" : "5f613358148239d0292c0cef674a3c2314737f9e", - "version" : "2.4.1" + "revision" : "d30a5fad881137e2267f96a8e3fc35c58999bb94", + "version" : "8.6.2" } }, { @@ -27,6 +27,15 @@ "revision" : "5d9bdaa4228b381639fff09403e39a04926e2dbe", "version" : "0.7.1" } + }, + { + "identity" : "swift-markdown-ui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/swift-markdown-ui", + "state" : { + "revision" : "5f613358148239d0292c0cef674a3c2314737f9e", + "version" : "2.4.1" + } } ], "version" : 3 diff --git a/ios/WildGrowth/电子成长.xcodeproj/xcshareddata/xcschemes/WildGrowth.xcscheme b/ios/WildGrowth/电子成长.xcodeproj/xcshareddata/xcschemes/WildGrowth.xcscheme index 56997d8..3b33dee 100644 --- a/ios/WildGrowth/电子成长.xcodeproj/xcshareddata/xcschemes/WildGrowth.xcscheme +++ b/ios/WildGrowth/电子成长.xcodeproj/xcshareddata/xcschemes/WildGrowth.xcscheme @@ -24,7 +24,7 @@ + buildConfiguration = "Debug-Local">