OCR 模型按需下载(P8 瘦身): - 移除 plugins/ppocr/assets/ 内置模型(det 9.8MB + rec 21MB + dict),APK 减包 ~31MB - 新增 modelDownloader.ts:优先从 HuggingFace/CDN 下载,兜底从 APK assets 拷贝 - OcrModule.kt 新增 setModelDir,支持从 filesystem 加载模型,回退 assets 兼容旧用户 - settingsStore 持久化 ocrModelVersion / ocrModelDir - 自动化页集成模型状态检查与一键下载 UI OCR 三层独立控制: - OcrProcessorConfig 从单一 aiVisionEnabled 拆分为 layer1/2/3 三个独立开关 - 设置页可按层启停(L1 正则规则 / L2 本地 OCR / L3 AI Vision) - OCR 处理增加耗时与字符数日志 日志系统升级: - logger.ts 新增 LogFileBackend 抽象,支持磁盘持久化(按日期 app-YYYY-MM-DD.log) - 新增 logBackend.ts(ExpoLogFileBackend)+ 日志中心页 settings/logs.tsx - 日志中心:实时缓冲 + 历史文件、4 级过滤、Tag/关键词搜索、JSON 展开、分享导出、7 天过期清理 - _layout.tsx 启动时初始化文件后端 + 日志脱敏(验证码/卡号) 原生浮层 UI 主题同步(P6): - 新增 floatingUiConfig.ts:JS 侧从 theme tokens + i18n 构建 FloatingUiConfig 推送原生 - 新增 FloatingUiConfigStore.kt:SharedPreferences 存储,三浮层组件读取 - FloatingBillView 重设计:颜色/文案走配置、新增币种 chip、金额校验改 BigDecimal - FloatingHelper / FloatingTip 同步适配 - _layout.tsx 新增 FloatingUiConfigSyncer,主题/语言切换自动推送 UI 与组件增强: - FormModal 新增 select/dropdown 控件、行内布局(row/flex)、联动回调 onValuesChange - 新增 Touchable 通用触摸组件、AccountCreateModal 快速建账弹窗 - 信用卡页展示账单周期/到期还款日/本期应还/剩余可用额度,关联账户改下拉选择 - AI 设置页重做:OpenAI/Gemini/DeepSeek 预设 + 默认 URL/模型 - 引导页新增 Android 权限检查步骤(无障碍/通知/短信/存储/悬浮窗) 去重优化: - 对手方匹配改为模糊包含(includes),双方均无对手方时判定低置信度重复 - DedupResult 新增 matchedItem 返回匹配对比项 文档重构: - README.md 重写为入口索引(品牌更新 + 模块概览 + 文档导航表) - 新增 AGENTS.md(AI 助手贡献指南)、docs/architecture.md(Mermaid 数据流/分层/OCR 级联图) - 新增 docs/development.md(环境/命令/编码规范/测试/提交规范)、plugins/README.md - UI 重设计文档(design spec + p1-p8)移入 docs/design/ 其他: - i18n 新增权限/信用卡详情/日志中心/AI 设置等翻译键 - ppocr Config Plugin 修复 import 注入去重;size-optimization 增强 - 新增测试:logger.test.ts、floating-ui-config.test.ts
193 lines
8.6 KiB
Markdown
193 lines
8.6 KiB
Markdown
# P7:权限快捷引导 Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax. **不执行任何 git 操作**(用户要求,改动留工作区)。不创建额外任务清单。
|
||
|
||
**Goal:** 引导页权限步骤从纯文字改可操作状态清单;automation 页补齐通知监听/短信/存储三项权限的状态检测与快速跳转按钮。不新增原生权限,只加 UI 入口和一座通知监听状态检测 bridge 方法。
|
||
|
||
---
|
||
|
||
## 背景决策
|
||
|
||
- **不新增权限**。所有权限已在 AndroidManifest 中声明(通过 Config Plugin),P7 只加 UI 入口。
|
||
- **通知监听状态检测**需要一个原生 bridge 方法(`isNotificationListenerEnabled`),因为 RN 侧无法直接检查。用 `NotificationManager.getEnabledListenerPackages()`。
|
||
- **短信 + 存储**用 React Native 内置 `PermissionsAndroid` API。
|
||
- **引导页 UI** 沿用现有 Card + Button 组件,与 P1-P5 设计系统一致。
|
||
|
||
---
|
||
|
||
### Task 1: 新增桥接方法 `isNotificationListenerEnabled`
|
||
|
||
**Files:**
|
||
- Modify: `plugins/accessibility/android/AccessibilityBridgeModule.kt`
|
||
- Modify: `src/services/accessibilityBridge.ts`
|
||
|
||
- [ ] **Step 1: AccessibilityBridgeModule.kt 加方法**
|
||
|
||
```kotlin
|
||
@ReactMethod
|
||
fun isNotificationListenerEnabled(promise: Promise) {
|
||
try {
|
||
val pm = reactContext.packageManager
|
||
val enabledListeners = android.app.NotificationManager::class.java
|
||
.getMethod("getEnabledListenerPackages")
|
||
.invoke(reactContext.getSystemService(Context.NOTIFICATION_SERVICE)) as? List<String>
|
||
val myPkg = reactContext.packageName
|
||
promise.resolve(enabledListeners?.any { it.startsWith(myPkg) } == true)
|
||
} catch (e: Exception) {
|
||
promise.resolve(false)
|
||
}
|
||
}
|
||
```
|
||
|
||
**注意**:`getEnabledListenerPackages()` 在 Android 11+ 需 `isNotificationListenerEnabled(ComponentName)`;优先用反射避免 API level lint 错误,反射失败返回 false。
|
||
|
||
- [ ] **Step 2: JS 侧 bridge 接口**
|
||
|
||
`NativeAccessibilityBridge` 加 `isNotificationListenerEnabled(): Promise<boolean>;`
|
||
|
||
- [ ] **Step 3: 编译验证**
|
||
|
||
`cd android && ./gradlew :app:compileDebugKotlin` → BUILD SUCCESSFUL
|
||
|
||
---
|
||
|
||
### Task 2: 引导页权限步骤(`_onboarding.tsx` 重写 permissions 步骤)
|
||
|
||
**Files:**
|
||
- Modify: `src/app/_onboarding.tsx`
|
||
- Modify: `src/i18n/zh.ts`、`src/i18n/en.ts`(少量新键)
|
||
|
||
- [ ] **Step 1: i18n 新键(zh/en 对等)**
|
||
|
||
在 `onboarding` section 加:
|
||
```
|
||
permAccessibility 无障碍服务 / Accessibility Service
|
||
permNotification 通知监听 / Notification Listener
|
||
permOverlay 悬浮窗 / Overlay
|
||
permSms 短信读取 / SMS
|
||
permStorage 相册/存储 / Photos & Storage
|
||
permGranted 已授权 / Granted
|
||
permNotGranted 未授权 / Not Granted
|
||
permOpenSettings 前往设置 / Open Settings
|
||
permRequest 请求权限 / Request
|
||
```
|
||
|
||
- [ ] **Step 2: 重写 permissions 步骤**
|
||
|
||
替换 `_onboarding.tsx:48` 的 `key: 'permissions'` 步骤内容。在当前 description 下方加权限状态清单(Card 包裹,每行:图标 + 名称 + 状态点 + 操作按钮)。
|
||
|
||
权限列表:
|
||
1. **无障碍服务**(`BIND_ACCESSIBILITY_SERVICE`)—— 最核心。检测 `serviceRunning`(从 `getAccessibilityBridge().isServiceRunning()` 异步取)。未授权 → 跳转 `ACCESSIBILITY_SETTINGS`
|
||
2. **通知监听**(`BIND_NOTIFICATION_LISTENER_SERVICE`)—— 检测 `bridge.isNotificationListenerEnabled()`。未授权 → 跳转 `ACTION_NOTIFICATION_LISTENER_SETTINGS`
|
||
3. **短信**(`RECEIVE_SMS`)—— `PermissionsAndroid.check()`。未授权 → `PermissionsAndroid.request()`
|
||
4. **存储**(`READ_MEDIA_IMAGES`,Android 13+ 用 `READ_MEDIA_IMAGES`,否则 `READ_EXTERNAL_STORAGE`)—— 同上
|
||
|
||
**实现细节**:
|
||
- `const [permStates, setPermStates] = useState<Record<string, boolean | null>>({})` —— null = 加载中
|
||
- `useEffect(() => { checkAllPermissions(); }, [])` —— 步骤切到 permissions 时触发
|
||
- `checkAllPermissions`:调 `PermissionsAndroid.check()` 检查短信和存储;调 bridge 检查无障碍和通知监听
|
||
- 每行渲染:`Ionicons` 图标 + `permLabel` + 右侧状态文字(绿「已授权」/ 灰「未授权」)+ 未授权时显示操作按钮
|
||
- 「全部已授权」时显示一个大绿色勾 + 提示文字,按钮不显示
|
||
|
||
**权限 API 版本适配**:
|
||
- `PermissionsAndroid.PERMISSIONS` 里 Android 13+ 是 `READ_MEDIA_IMAGES`,低版本用 `READ_EXTERNAL_STORAGE`
|
||
- 统一用 `Platform.Version >= 33 ? 'android.permission.READ_MEDIA_IMAGES' : 'android.permission.READ_EXTERNAL_STORAGE'`
|
||
|
||
- [ ] **Step 3: 验证**
|
||
|
||
Run: `npm run typecheck && npm test` → 全绿
|
||
Run: `grep -rn "onboarding.perm" src/i18n/zh.ts src/i18n/en.ts` → 确认键对等
|
||
|
||
---
|
||
|
||
### Task 3: automation 页补齐权限入口
|
||
|
||
**Files:**
|
||
- Modify: `src/app/automation/index.tsx`
|
||
- Modify: `src/i18n/zh.ts`、`src/i18n/en.ts`(少量新键)
|
||
|
||
- [ ] **Step 1: i18n 新键**
|
||
|
||
在 `automation` section 加:
|
||
```
|
||
notificationTitle 通知监听服务 / Notification Listener
|
||
notificationEnabled 已启用 / Enabled
|
||
notificationDisabled 未启用 / Disabled
|
||
notificationOpenSettings 前往系统设置 / Open Settings
|
||
smsPermissionTitle 短信读取权限 / SMS Permission
|
||
smsPermissionGranted 已授权 / Granted
|
||
smsPermissionRequest 请求权限 / Request
|
||
storagePermissionTitle 存储权限 / Storage Permission
|
||
storagePermissionGranted 已授权 / Granted
|
||
storagePermissionRequest 请求权限 / Request
|
||
```
|
||
|
||
- [ ] **Step 2: automation 页加权限卡片**
|
||
|
||
在现有「无障碍服务状态与控制」Card **之后**加一个新 Card:`<Card title={t('automation.otherPermissions')}>`。
|
||
|
||
内容三行:
|
||
1. **通知监听**:异步 `bridge.isNotificationListenerEnabled()` → 状态文字 + 未启用时「前往系统设置」按钮(`Linking.sendIntent('android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS')`)
|
||
2. **短信**:`PermissionsAndroid.check('android.permission.RECEIVE_SMS')` + `PermissionsAndroid.request()`
|
||
3. **存储**:`PermissionsAndroid.check(StoragePermission)` + `PermissionsAndroid.request()`
|
||
|
||
**实现模式**:
|
||
```tsx
|
||
const [notifEnabled, setNotifEnabled] = useState(false);
|
||
const [smsGranted, setSmsGranted] = useState(false);
|
||
const [storageGranted, setStorageGranted] = useState(false);
|
||
useEffect(() => {
|
||
// 异步获取各权限状态
|
||
checkNotif(); checkSms(); checkStorage();
|
||
}, []);
|
||
```
|
||
|
||
每行渲染:`Ionicons` 图标 + 权限名称 + 状态文字 + 未授权时的按钮。
|
||
|
||
- [ ] **Step 3: 验证**
|
||
|
||
Run: `npm run typecheck && npm test` → 全绿
|
||
|
||
---
|
||
|
||
### Task 4: 编译 + 测试 + 终审
|
||
|
||
- [ ] **Step 1: 编译验证**
|
||
|
||
`cd android && ./gradlew :app:compileDebugKotlin` → BUILD SUCCESSFUL
|
||
|
||
- [ ] **Step 2: 全量测试**
|
||
|
||
`npm test` → 全绿;`npm run typecheck` → 0 错误
|
||
|
||
- [ ] **Step 3: 审计**
|
||
|
||
```bash
|
||
grep -rn "#[0-9A-Fa-f]\{6\}" src/ --include="*.ts" --include="*.tsx" | grep -v "theme/presets.ts\|theme/palette.ts" # 无输出
|
||
grep -rn "fontFamily" src/app/_onboarding.tsx src/app/automation/index.tsx # 无输出
|
||
grep -rnE "📊|💰|💸|📝|🔄|🎉|✨|⚠|📌|🌟" src/i18n/ src/app/_onboarding.tsx src/app/automation/index.tsx # 无输出(预存 i18n fallbackWarn ⚠ 除外)
|
||
```
|
||
|
||
- [ ] **Step 4: 手工走查(需设备)**
|
||
|
||
1. 清数据 → 启动应用 → 引导页第 5 步(权限)→ 看到 4 个权限状态
|
||
2. 点未授权的无障碍 → 跳系统无障碍设置 → 返回看到状态已变
|
||
3. 点未授权的通知监听 → 跳系统通知设置
|
||
4. 点短信 → 弹出系统权限对话框
|
||
5. 引导页全部完成后 → 进入 automation 页 → 看到新增的权限卡片
|
||
|
||
---
|
||
|
||
## 终审修订(已实施,以实际代码为准)
|
||
|
||
**终审日期**:2026-07-22,审计 598 测试全绿、typecheck 干净、Kotlin BUILD SUCCESSFUL。
|
||
|
||
| # | 级别 | 问题 | 处理 |
|
||
|---|---|---|---|
|
||
| — | — | 无 Critical / Important 发现 | — |
|
||
|
||
**偏差说明**:
|
||
1. `onboarding.permOverlay`(悬浮窗)未加入引导页——计划 Task 2 决策列为可选项,实际未实现。不影响核心功能(sideload 自动授予 + 无障碍运行时浮窗走 ACCESSIBILITY_OVERLAY 不依赖此权限)。
|
||
2. T3 中 `Platform.Version >= 33` 改为 `Number(Platform.Version) >= 33`——React Native 的 `Platform.Version` 类型为 `string | number`,直接比较会 TS 报错。
|
||
3. T1 的 `isNotificationListenerEnabled` 使用反射而非 `enabledListenerPackages` 属性——minSdk=24 < API 27,反射是兼容方案。
|