```
feat: 添加自定义提醒功能并优化项目配置 - 添加 CustomReminder 模型支持自由文案周期性提醒功能 - 实现自定义提醒的 UI 界面,包括新建、编辑和列表展示 - 集成本地通知服务支持自定义提醒的时间触发 - 更新项目配置文件添加应用显示名称和加密声明 - 修正 iOS 部署目标版本从 26.0 到 17.0 - 修复 FileDownloader 中的线程安全问题 - 优化 ModelManifest 和 Localization 的并发安全性 - 扩展本地化字符串支持多语言提醒相关文本 - 调整项目支持平台范围仅保留 iphoneos 和 iphonesimulator ```
This commit is contained in:
@@ -10,6 +10,7 @@ import UserNotifications
|
||||
enum ReminderService {
|
||||
|
||||
static let idPrefix = "kangkang.reminder."
|
||||
static let customIdPrefix = "kangkang.custom."
|
||||
|
||||
enum AuthState: String {
|
||||
case granted, denied, notDetermined, provisional
|
||||
@@ -51,34 +52,45 @@ enum ReminderService {
|
||||
/// 调用方在 `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 = String(appLoc: "该测\(reminder.displayName)了")
|
||||
content.body = String(appLoc: "在「+ 新建 → 指标记录 → \(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)
|
||||
}
|
||||
guard reminder.enabled else { return }
|
||||
await schedule(
|
||||
idBase: "\(idPrefix)\(reminder.metricId)",
|
||||
title: String(appLoc: "该测\(reminder.displayName)了"),
|
||||
body: String(appLoc: "在「+ 新建 → 指标记录 → \(reminder.displayName)」记录一次"),
|
||||
hour: reminder.hour,
|
||||
minute: reminder.minute,
|
||||
weekdays: reminder.weekdays,
|
||||
thread: "kangkang.reminder.\(reminder.metricId)"
|
||||
)
|
||||
}
|
||||
|
||||
/// 取消某个 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)
|
||||
cancelBase("\(idPrefix)\(metricId)")
|
||||
}
|
||||
|
||||
// MARK: - 自由提醒(CustomReminder)
|
||||
|
||||
/// 取消并按当前设置重排一条自由提醒。调用方在 `CustomReminder` save 之后调用。
|
||||
static func sync(_ reminder: CustomReminder) async {
|
||||
cancel(customId: reminder.id)
|
||||
guard reminder.enabled else { return }
|
||||
let title = reminder.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let body = reminder.note.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
await schedule(
|
||||
idBase: "\(customIdPrefix)\(reminder.id.uuidString)",
|
||||
title: title.isEmpty ? String(appLoc: "提醒") : title,
|
||||
body: body.isEmpty ? String(appLoc: "到点啦,记得完成") : body,
|
||||
hour: reminder.hour,
|
||||
minute: reminder.minute,
|
||||
weekdays: reminder.weekdays,
|
||||
thread: "\(customIdPrefix)\(reminder.id.uuidString)"
|
||||
)
|
||||
}
|
||||
|
||||
/// 取消某条自由提醒的所有 pending 通知。
|
||||
static func cancel(customId: UUID) {
|
||||
cancelBase("\(customIdPrefix)\(customId.uuidString)")
|
||||
}
|
||||
|
||||
/// 全清。Me Tab 一键关闭所有提醒时用。
|
||||
@@ -86,9 +98,42 @@ enum ReminderService {
|
||||
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
|
||||
}
|
||||
|
||||
// MARK: - helpers
|
||||
// MARK: - 共享调度核心
|
||||
|
||||
private static func identifier(metricId: String, weekday: Int) -> String {
|
||||
"\(idPrefix)\(metricId).w\(weekday)"
|
||||
/// 把一条提醒按 weekdays 展开成 N 条 weekly-repeats 通知。
|
||||
/// `idBase` 是不含 `.w<weekday>` 后缀的稳定前缀;两类提醒共用本核心。
|
||||
private static func schedule(idBase: String,
|
||||
title: String,
|
||||
body: String,
|
||||
hour: Int,
|
||||
minute: Int,
|
||||
weekdays: [Int],
|
||||
thread: String) async {
|
||||
guard !weekdays.isEmpty else { return }
|
||||
let center = UNUserNotificationCenter.current()
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = .default
|
||||
content.threadIdentifier = thread
|
||||
|
||||
for weekday in weekdays {
|
||||
var comps = DateComponents()
|
||||
comps.hour = hour
|
||||
comps.minute = minute
|
||||
comps.weekday = weekday
|
||||
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: true)
|
||||
let request = UNNotificationRequest(identifier: "\(idBase).w\(weekday)",
|
||||
content: content,
|
||||
trigger: trigger)
|
||||
try? await center.add(request)
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消某个 idBase 下 7 个 weekday 的全部 pending 通知(不漏)。
|
||||
private static func cancelBase(_ idBase: String) {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
let ids = (1...7).map { "\(idBase).w\($0)" }
|
||||
center.removePendingNotificationRequests(withIdentifiers: ids)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user