|
|
@ -8,6 +8,7 @@ import user_portrait // 添加这行 |
|
|
|
|
|
|
|
|
import MediaPlayer |
|
|
import MediaPlayer |
|
|
import AVFoundation |
|
|
import AVFoundation |
|
|
|
|
|
import UserNotifications |
|
|
|
|
|
|
|
|
// 在文件顶部添加通知名称定义 |
|
|
// 在文件顶部添加通知名称定义 |
|
|
extension Notification.Name { |
|
|
extension Notification.Name { |
|
|
@ -693,12 +694,12 @@ private class MakePhoneCallHandler: FunctionHandler { |
|
|
|
|
|
|
|
|
/// 设置提醒处理器 |
|
|
/// 设置提醒处理器 |
|
|
private class SetReminderHandler: FunctionHandler { |
|
|
private class SetReminderHandler: FunctionHandler { |
|
|
private let eventStore: EKEventStore? |
|
|
private var eventStore: EKEventStore? = EKEventStore() |
|
|
|
|
|
|
|
|
init(eventStore: EKEventStore?) { |
|
|
init(eventStore: EKEventStore?) { |
|
|
self.eventStore = eventStore |
|
|
self.eventStore = eventStore |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func handle(arguments: [String: Any]) async throws -> String { |
|
|
func handle(arguments: [String: Any]) async throws -> String { |
|
|
guard let title = arguments["title"] as? String, |
|
|
guard let title = arguments["title"] as? String, |
|
|
let timeString = arguments["time"] as? String else { |
|
|
let timeString = arguments["time"] as? String else { |
|
|
@ -706,99 +707,113 @@ private class SetReminderHandler: FunctionHandler { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let content = arguments["content"] as? String ?? "" |
|
|
let content = arguments["content"] as? String ?? "" |
|
|
let result = await setReminder(title: title, content: content, time: timeString) |
|
|
let result = await setAlarmReminder(title: title, content: content, time: timeString) |
|
|
return "{\"result\": \"\(result.message)\"}" |
|
|
return "{\"result\": \"\(result.message)\"}" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private func setReminder(title: String, content: String, time: String) async -> Result { |
|
|
// 设置闹钟级提醒(主方法) |
|
|
guard let eventStore = eventStore else { |
|
|
func setAlarmReminder(title: String, content: String, time: String) async -> Result { |
|
|
return Result(success: false, message: "事件存储未初始化") |
|
|
// 1. 请求日历权限 |
|
|
|
|
|
let calendarStatus = await EKEventStore.authorizationStatus(for: .reminder) |
|
|
|
|
|
guard calendarStatus == .authorized else { |
|
|
|
|
|
let message = calendarStatus == .denied |
|
|
|
|
|
? "日历权限已拒绝,请前往「设置 → 隐私与安全 → 日历」开启" |
|
|
|
|
|
: "日历权限不足" |
|
|
|
|
|
return Result(success: false, message: message) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 请求日历权限 |
|
|
// 2. 请求关键通知权限(闹钟级提醒必需) |
|
|
let status = await requestCalendarAccess() |
|
|
let notificationCenter = UNUserNotificationCenter.current() |
|
|
print("[SetReminder] 权限检查结果: \(status.rawValue) - \(statusDescription(status))") |
|
|
do { |
|
|
|
|
|
let granted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .criticalAlert]) |
|
|
switch status { |
|
|
guard granted else { |
|
|
case .authorized: |
|
|
return Result(success: false, message: "请在「设置 → 你的App → 通知」中开启关键通知权限") |
|
|
// 权限已授权,继续执行 |
|
|
} |
|
|
break |
|
|
} catch { |
|
|
case .denied: |
|
|
return Result(success: false, message: "通知权限请求失败:\(error.localizedDescription)") |
|
|
return Result(success: false, message: "日历权限被拒绝,请前往 设置 > 隐私与安全 > 日历 中开启权限") |
|
|
|
|
|
case .restricted: |
|
|
|
|
|
return Result(success: false, message: "日历权限受限制,可能由于家长控制或企业策略限制") |
|
|
|
|
|
case .notDetermined: |
|
|
|
|
|
return Result(success: false, message: "日历权限未确定,请重试") |
|
|
|
|
|
@unknown default: |
|
|
|
|
|
return Result(success: false, message: "日历权限状态未知") |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 解析时间 |
|
|
// 3. 解析并验证时间(必须在当前时间10秒后) |
|
|
let formatter = DateFormatter() |
|
|
let formatter = DateFormatter() |
|
|
formatter.dateFormat = "yyyy-MM-dd HH:mm" |
|
|
formatter.dateFormat = "yyyy-MM-dd HH:mm" |
|
|
guard let date = formatter.date(from: time) else { |
|
|
formatter.timeZone = TimeZone.current |
|
|
|
|
|
guard let targetDate = formatter.date(from: time), targetDate.timeIntervalSinceNow > 10 else { |
|
|
return Result(success: false, message: "时间格式错误") |
|
|
return Result(success: false, message: "时间格式错误") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 创建提醒 |
|
|
// 4. 创建日历提醒(作为备份) |
|
|
|
|
|
let calendarResult = await createCalendarReminder(title: title, content: content, date: targetDate) |
|
|
|
|
|
guard calendarResult.success else { |
|
|
|
|
|
return calendarResult |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 5. 创建关键通知(闹钟级效果核心) |
|
|
|
|
|
return await createCriticalNotification(title: title, content: content, date: targetDate) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建日历提醒(备份用) |
|
|
|
|
|
private func createCalendarReminder(title: String, content: String, date: Date) async -> Result { |
|
|
|
|
|
guard let eventStore = eventStore else { |
|
|
|
|
|
return Result(success: false, message: "日历服务不可用") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
let reminder = EKReminder(eventStore: eventStore) |
|
|
let reminder = EKReminder(eventStore: eventStore) |
|
|
reminder.title = title |
|
|
reminder.title = title |
|
|
reminder.notes = content |
|
|
reminder.notes = content |
|
|
reminder.calendar = eventStore.defaultCalendarForNewReminders() |
|
|
|
|
|
|
|
|
|
|
|
let alarm = EKAlarm(absoluteDate: date) |
|
|
// 获取可用日历 |
|
|
reminder.addAlarm(alarm) |
|
|
guard let targetCalendar = eventStore.defaultCalendarForNewReminders() |
|
|
|
|
|
?? eventStore.calendars(for: .reminder).first(where: { $0.allowsContentModifications }) else { |
|
|
|
|
|
return Result(success: false, message: "无可用日历,请先在系统日历中创建日历") |
|
|
|
|
|
} |
|
|
|
|
|
reminder.calendar = targetCalendar |
|
|
|
|
|
reminder.addAlarm(EKAlarm(absoluteDate: date)) |
|
|
|
|
|
|
|
|
|
|
|
// 保存提醒 |
|
|
do { |
|
|
do { |
|
|
try eventStore.save(reminder, commit: true) |
|
|
try eventStore.save(reminder, commit: true) |
|
|
return Result(success: true, message: "提醒设置成功") |
|
|
return Result(success: true, message: "日历提醒已创建") |
|
|
} catch { |
|
|
} catch { |
|
|
return Result(success: false, message: "设置提醒失败:\(error.localizedDescription)") |
|
|
return Result(success: false, message: "日历提醒保存失败:\(error.localizedDescription)") |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func requestCalendarAccess() async -> EKAuthorizationStatus { |
|
|
|
|
|
return await withCheckedContinuation { continuation in |
|
|
|
|
|
// 首先检查当前权限状态 |
|
|
|
|
|
let currentStatus = EKEventStore.authorizationStatus(for: .reminder) |
|
|
|
|
|
print("[Calendar] 当前权限状态: \(currentStatus.rawValue) - \(statusDescription(currentStatus))") |
|
|
|
|
|
|
|
|
|
|
|
// 如果已经授权,直接返回 |
|
|
|
|
|
if currentStatus == .authorized { |
|
|
|
|
|
continuation.resume(returning: currentStatus) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果被拒绝,也直接返回(避免重复请求) |
|
|
|
|
|
if currentStatus == .denied { |
|
|
|
|
|
print("[Calendar] 权限已被拒绝,需要用户手动在设置中开启") |
|
|
|
|
|
continuation.resume(returning: currentStatus) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 请求权限 |
|
|
|
|
|
eventStore?.requestAccess(to: .reminder) { granted, error in |
|
|
|
|
|
let finalStatus = EKEventStore.authorizationStatus(for: .reminder) |
|
|
|
|
|
print("[Calendar] 权限请求完成 - granted: \(granted), error: \(error?.localizedDescription ?? "无")") |
|
|
|
|
|
print("[Calendar] 最终权限状态: \(finalStatus.rawValue) - \(self.statusDescription(finalStatus))") |
|
|
|
|
|
continuation.resume(returning: finalStatus) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 添加状态描述辅助方法 |
|
|
// 创建关键通知(实现闹钟级效果) |
|
|
private func statusDescription(_ status: EKAuthorizationStatus) -> String { |
|
|
private func createCriticalNotification(title: String, content: String, date: Date) async -> Result { |
|
|
switch status { |
|
|
let notificationContent = UNMutableNotificationContent() |
|
|
case .notDetermined: |
|
|
notificationContent.title = title |
|
|
return "未确定" |
|
|
notificationContent.body = content |
|
|
case .restricted: |
|
|
notificationContent.badge = 1 |
|
|
return "受限制" |
|
|
|
|
|
case .denied: |
|
|
// 关键配置:闹钟级声音(突破静音,最大音量) |
|
|
return "被拒绝" |
|
|
notificationContent.sound = UNNotificationSound.criticalSoundNamed( |
|
|
case .authorized: |
|
|
UNNotificationSoundName("default"), |
|
|
return "已授权" |
|
|
withAudioVolume: 1.0 |
|
|
@unknown default: |
|
|
) |
|
|
return "未知状态" |
|
|
|
|
|
|
|
|
// 关键配置:最高优先级(突破勿扰,全屏弹窗) |
|
|
|
|
|
notificationContent.interruptionLevel = .critical |
|
|
|
|
|
notificationContent.relevanceScore = 1.0 // 最高相关性分数 |
|
|
|
|
|
|
|
|
|
|
|
// 设置触发时间(精确到秒) |
|
|
|
|
|
let components = Calendar.current.dateComponents( |
|
|
|
|
|
[.year, .month, .day, .hour, .minute, .second], |
|
|
|
|
|
from: date |
|
|
|
|
|
) |
|
|
|
|
|
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false) |
|
|
|
|
|
|
|
|
|
|
|
// 发送通知请求 |
|
|
|
|
|
let request = UNNotificationRequest( |
|
|
|
|
|
identifier: "Alarm_\(UUID().uuidString)", |
|
|
|
|
|
content: notificationContent, |
|
|
|
|
|
trigger: trigger |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
|
try await UNUserNotificationCenter.current().add(request) |
|
|
|
|
|
return Result(success: true, message: "闹钟级提醒已设置,到点将强制响铃") |
|
|
|
|
|
} catch { |
|
|
|
|
|
return Result(success: false, message: "通知创建失败:\(error.localizedDescription)") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|