From 5714802c89dd866b2340dfd15518b96f73743793 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 16 Jun 2026 15:29:34 +0800 Subject: [PATCH] design: add dog park easter egg spec --- .gitignore | 3 +- .../2026-06-16-dog-park-easter-egg-design.md | 175 ++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-06-16-dog-park-easter-egg-design.md diff --git a/.gitignore b/.gitignore index b263d0c..b5b3675 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,6 @@ node_modules/ .omc/ logs/ .claude/ -docs/superpowers/ +docs/superpowers/* +!docs/superpowers/specs/ .worktrees/ diff --git a/docs/superpowers/specs/2026-06-16-dog-park-easter-egg-design.md b/docs/superpowers/specs/2026-06-16-dog-park-easter-egg-design.md new file mode 100644 index 0000000..c2824f1 --- /dev/null +++ b/docs/superpowers/specs/2026-06-16-dog-park-easter-egg-design.md @@ -0,0 +1,175 @@ +# 关于页面「小狗乐园」彩蛋设计 + +## 背景 + +在「关于鸭鸭日历」页面中,版本号目前是一个无实际功能的 `TextButton`。本设计为其增加一个隐藏彩蛋:连续点击版本号 7 次后进入「小狗乐园」页面,全屏循环播放一段彩蛋视频。 + +## 目标 + +- 提升应用趣味性,给用户一个可发现的隐藏彩蛋。 +- 保持现有架构:所有 UI 与业务逻辑留在 `:core`,`:app` 仅作为 Activity 壳。 +- 不引入过度复杂的依赖或状态管理。 + +## 设计决策摘要 + +| 决策项 | 选择 | 说明 | +|--------|------|------| +| 总点击次数 | 7 次 | 足够隐藏,又不会太难触发 | +| 提示开始时机 | 第 4 次点击 | 前 3 次静默,避免普通用户误触时被打扰 | +| 提示文案 | 「再点击 N 下进入小狗乐园」 | N 为剩余次数,简洁明确 | +| 超时重置 | 1.5 秒 | 与 Android 开发者选项等经典彩蛋保持一致节奏 | +| 进度持久化 | 不持久化 | 离开页面或超时即重置 | +| 提示组件 | 系统 Toast | 最符合「小气泡」语义,轻量 | +| 视频播放 | Media3 ExoPlayer | 功能强、与 Compose 集成成熟 | +| 视频位置 | `core/src/main/assets/video/enter_screen_bg1.mp4` | 与现有 `app_icon.webp` 等资源保持一致 | +| 视频显示 | 等比裁剪铺满(`RESIZE_MODE_ZOOM`) | 填满屏幕,视觉沉浸 | +| 声音 | 静音 | 不打扰用户 | +| 屏幕方向 | 跟随系统 | 不强制横竖屏 | +| 退出方式 | 系统返回键 | 支持预测性返回手势,Activity 自然 finish | +| 进入过渡动画 | 淡入 400ms | 营造进入彩蛋的仪式感 | +| 退出过渡动画 | 默认 slide | 保持现有返回风格 | + +## 触发机制(关于页面) + +### 状态 + +- 在 `AboutScreen` 内使用 `remember { mutableIntStateOf(0) }` 保存当前连续点击次数。 +- 计数为局部状态,不提升到 ViewModel,也不持久化。 +- `AboutScreen` 离开 Composition 时计数自然消失。 + +### 点击行为 + +| 当前点击次数 | 行为 | +|--------------|------| +| 1 ~ 3 | 计数 +1,无 Toast | +| 4 | Toast「再点击 3 下进入小狗乐园」 | +| 5 | Toast「再点击 2 下进入小狗乐园」 | +| 6 | Toast「再点击 1 下进入小狗乐园」 | +| 7 | 调用 `onNavigateToDogPark()`,进入彩蛋页面 | + +### 超时重置 + +每次点击启动/重启一个 `LaunchedEffect`: + +- 在 1.5 秒内收到下一次点击:取消旧 Job,计数 +1。 +- 1.5 秒内无新点击:Job 执行,计数重置为 0。 + +## 导航链路 + +``` +MainActivity + └── startActivityWithSlide → AboutActivity + └── onNavigateToDogPark → startActivityWithSlide → DogParkActivity +``` + +- `AboutActivity` 给 `AboutScreen` 新增回调 `onNavigateToDogPark: () -> Unit`。 +- `DogParkActivity` 继承 `BaseActivity`,自动获得 edge-to-edge 和 slide 转场支持。 + +## 小狗乐园页面 + +### 组件 + +- `DogParkScreen`:位于 `:core`,无业务参数,只负责全屏视频播放。 +- `DogParkActivity`:位于 `:app`,继承 `BaseActivity`,壳逻辑。 + +### 视频播放 + +- 使用 Media3 ExoPlayer + `PlayerView`。 +- 通过 Compose `AndroidView` 嵌入 `PlayerView`。 +- 配置: + - `resizeMode = RESIZE_MODE_ZOOM`:等比裁剪铺满全屏。 + - `useController = false`:不显示播放控件。 + - `player.volume = 0f`:静音。 + - `repeatMode = Player.REPEAT_MODE_ONE`:循环播放。 +- 媒体源 URI:`asset:///video/enter_screen_bg1.mp4`。 + +### 生命周期 + +- `onStart` → `player.play()` +- `onStop` → `player.pause()` +- `onDestroy` → `player.release()` +- 使用 `DisposableEffect` 绑定释放逻辑。 + +### 退出 + +- 用户按系统返回键或执行返回手势时 Activity finish。 +- 预测性返回手势由 Manifest 中的 `android:enableOnBackInvokedCallback="true"` 支持。 +- 退出动画保留 `BaseActivity` 默认 slide。 + +## 过渡动画 + +### 进入动画(淡入) + +- 新增 `app/src/main/res/anim/fade_in.xml`。 +- `DogParkActivity.onCreate` 中: + - Android 14+:`overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, R.anim.fade_in, R.anim.fade_out)` + - 低版本:`overridePendingTransition(R.anim.fade_in, R.anim.fade_out)`(在 `super.onCreate` 之后、`setContent` 之前调用) +- 淡入时长约 400ms。 + +### 退出动画 + +- 保留 `BaseActivity` 默认的 slide 返回动画,不覆盖。 + +## 资源 + +- 视频文件: + - 来源:`~/Pictures/enter_screen_bg1.mp4` + - 目标:`core/src/main/assets/video/enter_screen_bg1.mp4` +- 动画资源: + - 新增 `app/src/main/res/anim/fade_in.xml` + +## 依赖变更 + +### `gradle/libs.versions.toml` + +新增: + +```toml +[versions] +androidx-media3 = "1.6.1" + +[libraries] +androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" } +androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" } +``` + +### `core/build.gradle.kts` + +新增: + +```kotlin +implementation(libs.androidx.media3.exoplayer) +implementation(libs.androidx.media3.ui) +``` + +## 错误处理 + +- 视频加载/准备失败时,直接 `finishWithSlideBack()` 静默返回关于页面。 +- 不显示弹窗或 Snackbar,避免破坏彩蛋体验。 + +## 测试计划 + +### 单元测试 + +- 抽离纯函数 `getToastMessage(clickCount: Int): String?` 并测试: + - 1 ~ 3 返回 `null` + - 4 返回「再点击 3 下进入小狗乐园」 + - 5 返回「再点击 2 下进入小狗乐园」 + - 6 返回「再点击 1 下进入小狗乐园」 + - 7 返回 `null`(此时已跳转) + +### 手动测试 + +- 连续点击版本号 7 次,确认进入 `DogParkActivity`。 +- 点击过程中停顿 1.5 秒,确认计数重置。 +- 确认进入动画为淡入。 +- 确认视频全屏、静音、无控件、循环播放。 +- 确认系统返回键正常退出,并回到关于页面。 +- 确认低版本(< Android 14)和 Android 14+ 的淡入动画都生效。 + +## 未包含在本期 + +- 多次进入彩蛋后的不同内容。 +- 视频下载/动态更新。 +- 屏幕常亮保持。 +- 分享彩蛋入口。