Files
2026-02-05 16:06:49 +08:00

237 lines
9.0 KiB
Markdown
Raw Permalink 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.
# 「随心」主题Suixin Theme技术计划plan
## 0. 目标回顾
在 Home 现有「风景 / 纯色」主题基础上新增第三种主题「随心」:
- **输入**:问卷生成的用户画像 `U`(本地存储)
- **输出**Home 背景推荐颜色(以纯色为主)
- **规则**:复用「个性化背景颜色推荐算法」的 Base Theme/Neutral Theme 与 Hard Rules
- **计算时机**
- **冷启动App 进程级)**:计算一次并锁定 Base Theme
- **切换文案Home 上滑切下一条)**:在同一 Base Theme 内更新一次“当前颜色”
- **持久化**:主题选择与「随心」计算状态均持久化(避免回到 Home/重进页面时丢失)
- **多语言**TCzh-TW+ EN
## 1. 现状梳理(与改动点)
### 1.1 现有主题切换
- `ThemeMode` 当前为 `'scenery' | 'color'`
- `ThemeModal` 弹窗提供 2 个卡片切换
- `Home` 根据 `themeMode`
- `scenery`:背景图 + 默认底色
- `color`:从 `THEME_COLORS``index` 轮换纯色
- `ui.theme.mode` 已在 `AsyncStorage` 持久化
### 1.2 用户画像输入已就绪
客户端已将问卷映射为 `UserProfileV1_2(_Extended)` 并持久化(`user.profileScoring`),关键字段:
- `stage.unknown`Hard Ruleunknown → Neutral
- `need`(稀疏 one-hot`{ [needTag]: 1 }``{}`
- `emotion_score: number | null`
- `profile_confidence: number`
- `profile_answered`
## 2. 技术方案总览
### 2.1 「随心」算法在 Home 的落地形态
Home 不具备“长文案阅读页的 scroll”因此采用**“渐变单点采样”**来复用算法的连续插值模型:
- Base Theme 仍按 `need / stage` 选定并锁定Theme Lock
- 每次切换文案时,生成一个 \(t \in [0, 1]\),并计算:
\[
Color(t) = lerp(Color\_top, Color\_bottom, t)
\]
- 输出为单一 `hex` 纯色,作为 Home `backgroundColor`
- 全程 **不跨 need、不跨主题色系**,仅在同主题内移动
### 2.2 持久化与“只在冷启动/切换文案时计算”
为同时满足“持久化”与“冷启动时计算”:
- **持久化内容**:锁定的 `base_theme_id` + 用于生成 \(t\) 的 `seed` + 当前 `step_index` + `last_color`
- **冷启动计算**:当检测到“新一轮 App 启动会话”时,重新选择并锁定 `base_theme_id`,并重置/更新 `seed``step_index`
- **切换文案计算**:仅递增 `step_index`,在同一 `base_theme_id` 下更新 `last_color`
> 说明:冷启动检测以“进程级首次进入 Home”为准工程实现阶段会在 `_layout` 或全局单例中生成 boot 标记)。
## 3. 数据结构与存储设计
### 3.1 扩展主题枚举
-`ThemeMode` 扩展为:`'scenery' | 'color' | 'suixin'`
- 存储 key沿用 `ui.theme.mode`
### 3.2 新增「随心」状态存储
新增本地存储 key建议
- `ui.theme.suixin.state`
数据结构(建议):
```ts
type SuixinThemeStateV1 = {
schema_version: 1;
saved_at: string; // ISO8601
base_theme_id: 'neutral' | 'emotional_support' | 'parenting_pressure' | 'self_worth' | 'anxiety_relief' | 'rest_balance';
seed: string; // 用于生成 t 的稳定种子(可由 profile + 日期等派生)
step_index: number; // 每切换一条文案 +1
last_color: string; // "#RRGGBB"
};
```
### 3.3 冷启动会话标记Boot ID
为实现“仅冷启动时重置 base theme/seed”新增一个进程级 boot 标记(实现二选一):
- **方案 A推荐**:在 `app/_layout.tsx` 首次挂载时生成 `boot_id` 并写入内存单例(不落盘)
- **方案 B**:写入 `AsyncStorage`(例如 `app.boot.lastSeenAt`)并结合“本次运行内存标记”判定首次进入 Home
计划优先采用方案 A逻辑清晰且不污染存储。
## 4. 颜色算法实现细节Home 版本)
### 4.1 主题色盘常量
在客户端新增一个颜色模块(例如 `client/src/features/suixinTheme/`),内置:
- Base Theme5 套)+ Neutral1 套)
- 与设计文档保持一致的 `hex`
### 4.2 Base Theme 选择(锁定)
输入:`UserProfileScoring`
输出:`base_theme_id`
规则:
-`stage.unknown === 1``neutral`
-`need` 为空 `{}``neutral`
- 否则取 `Object.keys(need)[0]`
- 若 key 在枚举内 → 对应 Base Theme
- 否则 → `neutral`
### 4.3 t 的生成与“低感知变化”
为了让“切换文案”带来“流动感”但不跳变,采用**小步进**策略:
- 定义 `N = 12`(可调):表示从 \(0 \to 1\) 的分段数
- 每次切换文案:`step_index += 1`
- 计算:`t = (step_index % N) / (N - 1)`
> 该策略保证 \(t\) 在 \([0, 1]\) 内缓慢移动;到达 1 后回到 0 会有一次跳变。为进一步降低跳变,可改为往返波形:
>
> - `phase = step_index % (2*(N-1))`
> - `t = phase <= (N-1) ? phase/(N-1) : (2*(N-1)-phase)/(N-1)`
实现阶段默认采用**往返波形**,避免回卷突跳。
### 4.4 lerp 计算(严格线性)
- `lerp` 仅允许线性插值
- 颜色空间:先使用 sRGB 的逐通道线性插值(实现简单、可控);若后续需要更自然,可升级到线性空间插值,但仍保持线性模型
### 4.5 emotion/confidence 的约束接入
本期按“安全优先”策略落地:
-`emotion_score === null``emotion_score <= 0.3`:输出不做任何微扰(纯 lerp 结果)
- 亮度微扰(\(\Delta L \le \pm 2\%\))与饱和度上限为可选增强;若落地,将以 `profile_confidence` 作为开关条件,并确保不改变色系
## 5. UI 与交互实现计划
### 5.1 ThemeModal新增第三个主题卡片
-`client/components/home/ThemeModal.tsx`
- `ThemeMode` 扩展为包含 `'suixin'`
- 新增 `ThemeCard`:标题使用 i18n`t('theme.suixin')`
- 布局改造:由 2 卡横排改为 **3 卡自适应**`flexWrap` 或减小 gap/宽度),确保小屏不溢出
- 预览图:一期可复用 `theme_color.png` 作为占位;若有设计资源再替换为 `theme_suixin.png`
### 5.2 Home新增主题分支与颜色计算时机
`client/app/(app)/home.tsx`
-`themeMode === 'suixin'` 作为第三分支:
- 背景为纯色(`backgroundColor = suixinColor`
- 不显示风景图
- **冷启动**Home 首次进入时,读取用户画像与 `suixin.state`
- 若检测到新 boot 会话:重算并写入 `suixin.state`
- 否则:直接使用持久化的 `last_color`
- **切换文案**:在现有 `triggerNextContent` 成功切换索引后:
- 若当前主题为 `suixin`:递增 `step_index`,计算新的 `last_color`,并持久化
### 5.3 收藏Favorites背景记录兼容
`FavoriteItem.background` 当前对 `color``hex`,对 `scenery` 存图片索引。
- `suixin` 同样存 `hex`,与 `color` 分支一致即可
## 6. i18n 计划TC / EN
`client/src/i18n/locales/all.json` 增加:
- `theme.suixin`
- (可选)`theme.suixinDesc`(若 UI 后续展示描述)
英文命名采用语义化方案(本计划建议):
- EN`theme.suixin = "Ease"`
- TC`theme.suixin = "隨心"`
> 若后续品牌希望保留音译,也可改为 EN=`Suixin`,不影响技术实现。
## 7. 兼容性与迁移
- `ThemeMode` 的存储值新增 `'suixin'`
- 旧版本只会存 `'scenery'|'color'`,升级后兼容
- 若读取到未知值,继续回退 `'scenery'`
- 新增 `ui.theme.suixin.state`
- 若不存在,首次进入随心主题时初始化
## 8. 测试计划(最小可回归)
### 8.1 单元测试(推荐)
为颜色算法模块增加用例(可放在 `client/src/features/suixinTheme/__tests__/`
- `stage.unknown=1` → 必选 `neutral`
- `need={}` → 必选 `neutral`
- `need={rest_balance:1}` → 选 `rest_balance` Base Theme
- `step_index` 递增 → `t` 按往返波形变化且始终在 \([0,1]\)
- `emotion_score=null` / `<=0.3` → 不触发微扰逻辑
### 8.2 手动验收(与 spec 对齐)
- ThemeModal 能看到第三个主题并可切换
- 冷启动进入 Home随心背景根据画像选定主题色系
- 上滑切换文案:背景色在同主题内缓慢变化(无跨主题跳色)
- `stage.unknown=1``need` 跳过:背景为 Neutral Theme
- 切换语言:主题名称在 TC/EN 下正确显示
## 9. 风险与对策
- **三卡布局拥挤**:采用 `flexWrap`/缩小卡片尺寸,必要时改为横向滚动
- **“持久化”与“冷启动重算”矛盾**:以“状态落盘 + 冷启动重置 base theme/seed”方式兼容两者
- **颜色可读性风险**:一期先用主题中间色/插值结果,避免过饱和;必要时增加对比度检查(后续迭代)
## 10. 里程碑拆分(实现顺序)
- **M1基础接入**
- 扩展 `ThemeMode`ThemeModal 增加第三项与 i18n
- Home 增加 `suixin` 分支,背景可显示(先用 neutral 兜底)
- **M2算法落地 + 持久化**
- 新增 suixin 颜色模块Base/Neutral、pickTheme、lerp、t 生成)
- 新增 `suixin.state` 存取与冷启动/切换文案更新
- **M3回归与体验优化**
- 收藏背景记录兼容
- 测试补齐与边界修正unknown/跳过/缺画像)