主体:多语言支持(简体中文源 + 英/日/韩)
- 基础设施:Localizable.xcstrings(String Catalog,sourceLanguage=zh-Hans)
+ pbxproj developmentRegion/knownRegions 注册 en/ja/ko
- 全部硬编码 Locale("zh_CN") → Locale.current;中文 dateFormat → Date.FormatStyle(跟随系统)
- UI 中文字面量统一为 String(appLoc:)(显式绑定所选语言 bundle+locale,即时切换)
Text 字面量走环境 \.locale + Bundle 重定向
- 549 个 catalog key 全部 en/ja/ko 翻译完成(0 未翻译)
- App 内语言切换:我的 → 语言(LanguageManager + 即时生效,无需重启)
- 双用预设(症状/监测指标/慢病)本地化:static→computed 避免缓存
注:本提交为 WIP,一并打包了并行进行的功能模块
(HealthExport 健康导出、Security/Face ID 锁、DiaryAssist 日记 AI 辅助)
及 App 图标、CLAUDE.md、docs/scripts。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.2 KiB
Face ID 启动锁 — 设计文档
日期:2026-05-30(W2) 作者:link2026 + Claude 关联卖点:#4 隐私三件套(系统级加密 + Face ID + 永久删除) 优先级:P1(CLAUDE.md §6 / §8 / §11,原排期 W5 末,提前实现)
1. 一句话定位
可选的 Face ID/Touch ID 启动锁(默认关)。开启后,冷启动与「后台超过 1 分钟再回前台」都需要系统认证才能进入 App;失败可用设备密码兜底。完全基于系统 LocalAuthentication,不自造任何密码学(对齐红线 §10.2)。
2. 设计决策(已与用户确认)
| 决策点 | 选择 |
|---|---|
| 锁屏时机 | 冷启动 + 后台超过宽限才重锁 |
| 后台宽限 | 60 秒 |
| 认证策略 | .deviceOwnerAuthentication(Face ID/Touch ID 优先,自动跳设备密码兜底,避免锁死) |
| 默认状态 | 关(§6) |
| 开关位置 | 「我的」Tab 现有的 Face ID 卡,改为可交互 Toggle |
| 任务切换器隐私遮罩 | 加,仅锁开启时生效(进 .inactive/.background 盖品牌遮罩,防多任务快照泄露;默认关用户无感) |
关于 §6「截屏黑屏防护…不做」:那条针对的是截图防护(iOS 无官方 API);本设计的任务切换器遮罩是 .inactive 盖视图,是官方支持的标准做法,性质不同。
3. 架构
KangkangApp
└─ WindowGroup { AppLockContainer { RootView() } } ← 仅包一层,RootView 零改动(§10.7)
│
┌─────────────┴──────────────────────────────┐
│ AppLockContainer<Content> │
│ @Environment(\.scenePhase) │
│ 渲染 content │
│ .overlay { if isLocked → LockScreen}│
│ .overlay { else if showsCover → PrivacyCover}│
│ onAppear → handleAppear(); │
│ onChange(scenePhase) → handleScenePhase() │
└─────────────────────────────────────────────┘
│ 读写
┌─────────────┴──────────────────────────────┐
│ AppLock.shared (@MainActor @Observable) │ ← Security/AppLock.swift
│ enabled ←→ UserDefaults("faceIDLockEnabled")│
│ isLocked / showsPrivacyCover │
│ biometryAvailable / biometryLabel │
│ gracePeriod = 60s,lastBackgroundedAt │
│ authenticate() / enableWithAuth() / disable()│
└──────────────────────────────────────────────┘
单例写法与项目既有 ModelDownloadService.shared 一致(@MainActor @Observable final class + static let shared)。
4. 触发逻辑(状态机)
| scenePhase / 事件 | 行为 |
|---|---|
容器 onAppear(冷启动) |
enabled 为真且尚未冷启动锁过 → isLocked = true + 触发认证 |
.background |
lastBackgroundedAt = now;showsPrivacyCover = enabled |
.inactive(任务切换器) |
showsPrivacyCover = enabled && !isLocked |
.active |
隐藏遮罩;若 enabled && !isLocked && 离开 > 60s → isLocked = true;若 isLocked → 触发认证;清空 lastBackgroundedAt |
| 认证成功 | isLocked = false |
| 认证失败/取消 | 保持锁定,锁屏提供「解锁」按钮重试(isAuthenticating 防重入,不重复弹窗) |
冷启动时 scenePhase 初值为 .active 不触发 onChange,由 handleAppear() 负责冷启动锁;两路触发由 isAuthenticating 守卫去重。
5. 能力探测与兜底
refreshAvailability():LAContext.canEvaluatePolicy(.deviceOwnerAuthentication)→biometryAvailable;读biometryType决定文案(Face ID / Touch ID / 密码)。- 设备未设密码/无生物识别 →
biometryAvailable = false,「我的」开关置灰,副标题「本设备未设置 Face ID 或密码」。 - 认证全程系统弹窗;失败/取消不抛错给 UI,只是停留锁屏。
6. 文件清单
| 文件 | 改动 |
|---|---|
康康/Security/AppLock.swift |
新增:单例 + LAContext 封装 + 触发逻辑 |
康康/Security/AppLockContainer.swift |
新增:包裹层 + scenePhase 驱动 + 两个 overlay |
康康/Security/LockScreenView.swift |
新增:LockScreenView + PrivacyCoverView |
康康/App/KangkangApp.swift |
RootView() → AppLockContainer { RootView() } |
康康/Features/Me/MeView.swift |
静态 Face ID 卡 → 可交互 Toggle 卡 |
康康.xcodeproj/project.pbxproj |
加 INFOPLIST_KEY_NSFaceIDUsageDescription(Debug + Release) |
工程用文件系统同步组,新增 Security/ 下的源文件自动纳入编译,无需手改 pbxproj 注册。
7. UI
锁屏(LockScreenView,全遮罩,走 Tj tokens):
🔒 (lock glyph)
康康 已锁定
你的健康档案已加密保护
[ Face ID 解锁 ] ← onAppear 自动触发一次认证;按钮文案随设备能力变
隐私遮罩(PrivacyCoverView):品牌色底 + app 名,无交互,仅用于遮挡多任务快照。
「我的」Face ID 卡:Toggle 开启时先认证一次(成功才置 enabled),关闭直接关。副标题动态:「已开启 · Face ID」/「关闭」/「本设备未设置 Face ID 或密码」。
8. 红线对齐(CLAUDE.md §10)
- 不自造密码学,只用系统
LocalAuthentication✅ - 默认关,可选开关 ✅
- 不引云 ✅
- 不重构 Tab/RecordSheet 骨架,只加一层包裹 ✅
- 清单内功能(§6/§8/§11 明列 Face ID 启动锁)✅
9. 测试与验收
- 单元测试价值低(核心是系统弹窗 + scenePhase),不强求;
AppLock的宽限判定逻辑可抽纯函数测(可选)。 - 真机验收:① 开关开启走 Face ID;② 杀进程冷启动需认证;③ 后台 <60s 回来不锁、>60s 回来锁;④ 多任务切换器快照被遮罩;⑤ 关 Face ID 录入(模拟失败)能跳设备密码;⑥ 默认关时全程无感。
- 模拟器:Features → Face ID → Enrolled / Matching Face 可模拟。