feat(capture): 统一报告捕获流程并集成视觉语言模型识别
- 替换 QuickCaptureFlow 和 ArchiveFlow 为 UnifiedCaptureFlow 统一流程 - 新增 VLSession 封装 Qwen2.5-VL 模型进行图像文本推理 - 实现 AIRuntime 中 VL 模型的准备和分析功能 - 添加 VLPrompts 定义体检化验单识别的 JSON 输出模板 - 创建 CaptureReviewForm 提供 VL 解析结果的可编辑表单界面 - 集成 VisionKit 文档扫描器支持真机多页文档扫描 - 为模拟器实现 PhotosPicker 回退方案选择已有照片 - 在 RootView 中统一使用 UnifiedCaptureFlow 处理快速和归档流程 - 添加 CustomMetricEditor 支持自定义监测指标的创建编辑删除 - 扩展 KangkangApp 模型配置以支持新数据类型 - 实现档案列表中症状结束功能通过时间线行点击触发
This commit is contained in:
94
康康/Services/ReminderService.swift
Normal file
94
康康/Services/ReminderService.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
/// 周期性指标提醒的本地通知调度。
|
||||
/// 同一 `metricId` 在 iOS 通知中心展开成 N 条 weekly-repeats 通知,id 形如
|
||||
/// `kangkang.reminder.<metricId>.w<weekday>`,方便按 weekday 单独 cancel。
|
||||
///
|
||||
/// 数据存 SwiftData `MetricReminder`;本服务只负责系统通知中心的同步,
|
||||
/// 不写 SwiftData。两边写入的协调由调用方负责。
|
||||
enum ReminderService {
|
||||
|
||||
static let idPrefix = "kangkang.reminder."
|
||||
|
||||
enum AuthState: String {
|
||||
case granted, denied, notDetermined, provisional
|
||||
}
|
||||
|
||||
// MARK: - authorization
|
||||
|
||||
static func currentAuthState() async -> AuthState {
|
||||
let settings = await UNUserNotificationCenter.current().notificationSettings()
|
||||
switch settings.authorizationStatus {
|
||||
case .authorized: return .granted
|
||||
case .denied: return .denied
|
||||
case .provisional: return .provisional
|
||||
case .ephemeral: return .granted
|
||||
case .notDetermined: return .notDetermined
|
||||
@unknown default: return .notDetermined
|
||||
}
|
||||
}
|
||||
|
||||
/// 申请通知权限。已 granted/denied 时直接返回当前状态。
|
||||
@discardableResult
|
||||
static func requestAuthorization() async -> AuthState {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
let settings = await center.notificationSettings()
|
||||
if settings.authorizationStatus != .notDetermined {
|
||||
return await currentAuthState()
|
||||
}
|
||||
do {
|
||||
let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
|
||||
return granted ? .granted : .denied
|
||||
} catch {
|
||||
return .denied
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - upsert / cancel
|
||||
|
||||
/// 取消该 metric 在通知中心所有 pending 通知,再按当前 enabled/时间/weekdays 重排。
|
||||
/// 调用方在 `MetricReminder` save 之后调用。
|
||||
static func sync(_ reminder: MetricReminder) async {
|
||||
cancel(metricId: reminder.metricId)
|
||||
guard reminder.enabled, !reminder.weekdays.isEmpty else { return }
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "该测\(reminder.displayName)了"
|
||||
content.body = "在「+ 新建 → 指标记录 → \(reminder.displayName)」记录一次"
|
||||
content.sound = .default
|
||||
content.threadIdentifier = "kangkang.reminder.\(reminder.metricId)"
|
||||
|
||||
for weekday in reminder.weekdays {
|
||||
var comps = DateComponents()
|
||||
comps.hour = reminder.hour
|
||||
comps.minute = reminder.minute
|
||||
comps.weekday = weekday
|
||||
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: true)
|
||||
let id = identifier(metricId: reminder.metricId, weekday: weekday)
|
||||
let request = UNNotificationRequest(identifier: id,
|
||||
content: content,
|
||||
trigger: trigger)
|
||||
try? await center.add(request)
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消某个 metric 的所有 pending 通知(7 个 weekday 一并取消,不漏)。
|
||||
static func cancel(metricId: String) {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
let ids = (1...7).map { identifier(metricId: metricId, weekday: $0) }
|
||||
center.removePendingNotificationRequests(withIdentifiers: ids)
|
||||
}
|
||||
|
||||
/// 全清。Me Tab 一键关闭所有提醒时用。
|
||||
static func cancelAll() {
|
||||
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
|
||||
}
|
||||
|
||||
// MARK: - helpers
|
||||
|
||||
private static func identifier(metricId: String, weekday: Int) -> String {
|
||||
"\(idPrefix)\(metricId).w\(weekday)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user