yayacal/docs/superpowers/specs/2026-06-16-dog-park-easter-egg-design.md

176 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 关于页面「小狗乐园」彩蛋设计
## 背景
在「关于鸭鸭日历」页面中,版本号目前是一个无实际功能的 `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+ 的淡入动画都生效
## 未包含在本期
- 多次进入彩蛋后的不同内容
- 视频下载/动态更新
- 屏幕常亮保持
- 分享彩蛋入口