Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
fdp 1 year ago
parent
commit
1daccf1d7e
  1. 6
      lib/data/services/ble_manager.dart
  2. 5
      lib/modules/pairing/controllers/pairing_controller.dart
  3. 1
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  4. 79
      local_plugins/amap_service/ios/Classes/AmapViewController.swift
  5. 58
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  6. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  7. 2
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt
  8. 190
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  9. 92
      local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift

6
lib/data/services/ble_manager.dart

@ -532,7 +532,7 @@ class BleManager extends GetxService {
try { try {
// 从.env文件直接读取环境变量 // 从.env文件直接读取环境变量
var serverurl = dotenv.env['SERVER_URL'] ?? ''; var serverurl = dotenv.env['SERVER_URL'] ?? '';
if (Get.deviceLocale?.countryCode != 'CN') { if (Get.locale?.countryCode != 'CN') {
serverurl = dotenv.env['HW_SERVER_URL'] ?? ''; serverurl = dotenv.env['HW_SERVER_URL'] ?? '';
} }
@ -860,9 +860,9 @@ class BleManager extends GetxService {
} }
} }
Future<List<Map<String, String>>?> getDeviceList(String devicename) async { Future<List<Map<String, String>>?> getDeviceList(String deviceName) async {
try { try {
final result = _bleService.getDeviceList(devicename); final result = _bleService.getDeviceList(deviceName);
return result; return result;
} catch (e) { } catch (e) {
Logger.error('获取设备信息: ${e.toString()}'); Logger.error('获取设备信息: ${e.toString()}');

5
lib/modules/pairing/controllers/pairing_controller.dart

@ -844,9 +844,11 @@ class PairingController extends GetxController with WidgetsBindingObserver {
license: ''), license: ''),
); );
if (existingDevice.id != -1) { if (existingDevice.id != -1) {
_bleManager.stopScan();
// 已经绑定 // 已经绑定
return 10; return 10;
} else { } else {
_bleManager.stopScan();
return 1; return 1;
} }
} }
@ -874,9 +876,11 @@ class PairingController extends GetxController with WidgetsBindingObserver {
license: ''), license: ''),
); );
if (existingDevice.id != -1) { if (existingDevice.id != -1) {
_bleManager.stopScan();
// 已经绑定 // 已经绑定
return 10; return 10;
} else { } else {
_bleManager.stopScan();
return 1; return 1;
} }
} }
@ -889,6 +893,7 @@ class PairingController extends GetxController with WidgetsBindingObserver {
} }
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
} }
_bleManager.stopScan();
if (product != null && product.devicetype == 2) { if (product != null && product.devicetype == 2) {
//ble设备 必须要扫描到结果 //ble设备 必须要扫描到结果
return 0; return 0;

1
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -19,6 +19,7 @@ extension Notification.Name {
static let navigationdEvent = Notification.Name("navigationdEvent") //导航事件 static let navigationdEvent = Notification.Name("navigationdEvent") //导航事件
static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位 static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位
static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件 static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件
static let exitnavigationdEvent = Notification.Name("exitnavigationdEvent") //退出导航事件
} }
class AgentServiceImpl: NSObject { class AgentServiceImpl: NSObject {
private let TAG = "AgentServiceImpl" private let TAG = "AgentServiceImpl"

79
local_plugins/amap_service/ios/Classes/AmapViewController.swift

@ -92,38 +92,39 @@ class AmapViewController: UIViewController {
} }
private func setupAMapComponents() { private func setupAMapComponents() {
self.addNSNotification();
// 1. 初始化 DriveView(必须在主线程) // 1. 初始化 DriveView(必须在主线程)
print("导航------------------ setupAMapComponents_1") // print("导航------------------ setupAMapComponents_1")
DispatchQueue.main.async { DispatchQueue.main.async {
print("导航------------------ setupAMapComponents_2") // print("导航------------------ setupAMapComponents_2")
self.driveView = AMapNaviDriveView(frame: self.view.bounds) self.driveView = AMapNaviDriveView(frame: self.view.bounds)
print("导航------------------ setupAMapComponents_3") // print("导航------------------ setupAMapComponents_3")
self.driveView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.driveView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
print("导航------------------ setupAMapComponents_4") // print("导航------------------ setupAMapComponents_4")
self.driveView.delegate = self self.driveView.delegate = self
print("导航------------------ setupAMapComponents_5") // print("导航------------------ setupAMapComponents_5")
self.view.addSubview(self.driveView) self.view.addSubview(self.driveView)
print("导航------------------ setupAMapComponents_6") // print("导航------------------ setupAMapComponents_6")
// 2. 初始化 DriveManager // 2. 初始化 DriveManager
self.driveManager = AMapNaviDriveManager.sharedInstance() self.driveManager = AMapNaviDriveManager.sharedInstance()
print("导航------------------ setupAMapComponents_7") // print("导航------------------ setupAMapComponents_7")
self.driveManager.delegate = self self.driveManager.delegate = self
print("导航------------------ setupAMapComponents_8") // print("导航------------------ setupAMapComponents_8")
self.driveManager.allowsBackgroundLocationUpdates = true self.driveManager.allowsBackgroundLocationUpdates = true
print("导航------------------ setupAMapComponents_9") // print("导航------------------ setupAMapComponents_9")
// 3. 确保对象非空再关联 // 3. 确保对象非空再关联
if let manager = self.driveManager, let view = self.driveView { if let manager = self.driveManager, let view = self.driveView {
print("导航------------------ setupAMapComponents_10") // print("导航------------------ setupAMapComponents_10")
manager.addDataRepresentative(view) manager.addDataRepresentative(view)
print("导航------------------ setupAMapComponents_11") // print("导航------------------ setupAMapComponents_11")
self.startNavigation() self.startNavigation()
print("导航------------------ setupAMapComponents_12") // print("导航------------------ setupAMapComponents_12")
} else { } else {
print("Error: driveManager or driveView is nil") print("Error: driveManager or driveView is nil")
} }
print("导航------------------ setupAMapComponents_13") // print("导航------------------ setupAMapComponents_13")
} }
} }
@ -144,26 +145,38 @@ class AmapViewController: UIViewController {
} }
@objc private func exitNavigation() { @objc private func exitNavigation() {
print("导航------------------ 退出导航,清理资源") print("导航------------------ 退出导航,清理资源")
// 停止导航 // 停止导航
driveManager.stopNavi() driveManager.stopNavi()
driveManager.removeDataRepresentative(driveView) driveManager.removeDataRepresentative(driveView)
driveManager.delegate = nil driveManager.delegate = nil
// 移除导航视图 // 移除导航视图
driveView.delegate = nil driveView.delegate = nil
driveView.removeFromSuperview() driveView.removeFromSuperview()
driveView = nil driveView = nil
// 销毁导航管理器 // 销毁导航管理器
driveManager = nil driveManager = nil
// 关闭当前页面,视情况而定 // 关闭当前页面 - 修复:使用正确的退出方式
// 如果你用的是导航控制器,可以pop if let navigationController = navigationController {
navigationController?.popViewController(animated: true) // 如果是在导航控制器中,使用 pop
// 或者如果是present出来的,dismiss navigationController.popViewController(animated: true)
// dismiss(animated: true, completion: nil) } else {
} // 如果是 present 出来的,使用 dismiss
dismiss(animated: true, completion: nil)
}
}
private func addNSNotification(){
NotificationCenter.default.addObserver(
forName: NSNotification.Name("ExitNavigation"),
object: nil,
queue: .main
) { _ in
self.exitNavigation()
}
}
} }
// MARK: - 代理方法 // MARK: - 代理方法

58
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -89,6 +89,7 @@ object BleService {
// 扫描相关 // 扫描相关
private lateinit var scanHandler: Handler private lateinit var scanHandler: Handler
private val scanResults = ArrayList<ScanResult>() private val scanResults = ArrayList<ScanResult>()
@Volatile
private var isScanning = false private var isScanning = false
// 是否是ota模式 // 是否是ota模式
@ -307,27 +308,32 @@ object BleService {
Log.i(TAG, "开始主动扫描BLE设备...") Log.i(TAG, "开始主动扫描BLE设备...")
scanResults.clear() scanResults.clear()
// Log.i(TAG, "liwei---------startScan 5") // Log.i(TAG, "liwei---------startScan 5")
// val filter = ScanFilter.Builder() val filter = ScanFilter.Builder()
// // .setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID)) // .setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID))
// .setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01)) // .setManufacturerData(0xEFAB, byteArrayOf(0xA0.toByte(), 0x01))
// .build() .build()
// Log.i(TAG, "liwei---------startScan 6") // val filters = uuids.mapNotNull { uuidStr ->
// val settings = ScanSettings.Builder() // try {
// .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // // 清理UUID字符串,移除非十六进制字符
// // .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH) // val cleanUuidStr = uuidStr.replace("[^0-9A-Fa-f]".toRegex(), "")
// .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH) // 使用ALL_MATCHES获取所有结果 //
// .setReportDelay(0) // 立即报告结果 // // 验证清理后的字符串长度(应该是4位十六进制字符)
// .build() // if (cleanUuidStr.length != 4) {
// val filter = ScanFilter.Builder() // Log.w(TAG, "无效的UUID格式: $uuidStr,清理后: $cleanUuidStr,长度: ${cleanUuidStr.length}")
// // 建议用 Service UUID 或 Device Name 替代 Manufacturer Data // return@mapNotNull null
// // .setServiceUuid(ParcelUuid(BleConst.PRIMARY_SERVICE_UUID)) // }
//// .setDeviceName(deviceName) // 更通用、更安全 //
// .build() // val fullUuid = "0000$cleanUuidStr-0000-1000-8000-00805f9b34fb"
val filters = uuids.map { uuidStr -> // Log.d(TAG, "扫描uuid: $fullUuid")
ScanFilter.Builder() //
.setServiceUuid(ParcelUuid(UUID.fromString(uuidStr))) // ScanFilter.Builder()
.build() // .setServiceUuid(ParcelUuid(UUID.fromString(fullUuid)))
} // .build()
// } catch (e: IllegalArgumentException) {
// Log.e(TAG, "构造UUID失败: $uuidStr", e)
// null
// }
// }
val settings = ScanSettings.Builder() val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 推荐:不会触发权限问题 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 推荐:不会触发权限问题
@ -335,7 +341,7 @@ object BleService {
.build() .build()
// Log.i(TAG, "liwei---------startScan 7") // Log.i(TAG, "liwei---------startScan 7")
try { try {
scanner.startScan(filters, settings, scanCallback) scanner.startScan(listOf(filter), settings, scanCallback)
isScanning = true isScanning = true
// Log.i(TAG, "liwei---------startScan 8") // Log.i(TAG, "liwei---------startScan 8")
// 设置扫描超时 // 设置扫描超时
@ -348,7 +354,7 @@ object BleService {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "启动扫描失败: ${e.message}", e) Log.e(TAG, "启动扫描失败: ${e.message}", e)
// Log.i(TAG, "liwei---------startScan 10") // Log.i(TAG, "liwei---------startScan 10")
// isScanning = false isScanning = false
return false return false
} }
} }
@ -468,10 +474,10 @@ object BleService {
val serviceUuids = val serviceUuids =
scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无"
// Log.d( Log.d(
// TAG, "liwei--------- 扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + TAG, "liwei--------- 扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " +
// "RSSI=${result.rssi}, 服务UUID=[$serviceUuids]" "RSSI=${result.rssi}, 服务UUID=[$serviceUuids]"
// ) )
val info = mutableMapOf( val info = mutableMapOf(
"name" to (device.name ?: "未知"), "name" to (device.name ?: "未知"),
"address" to device.address "address" to device.address

4
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt

@ -302,8 +302,8 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
} }
} }
"startScan" -> { "startScan" -> {
val devicename = call.argument<String>("devicename")?:"" val uuids = call.argument<List<String>>("uuids")?:emptyList()
val connected = BleService.startScan(devicename) val connected = BleService.startScan(uuids)
result.success(connected) result.success(connected)
} }
"getDeviceInfo" -> { "getDeviceInfo" -> {

2
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt

@ -164,7 +164,7 @@ class SystemFunctionHandler(private val context: Context? = null) {
handler = OpenRecorderHandler(context) handler = OpenRecorderHandler(context)
) )
// 注册退出交互函数 // 退出导航
client.registerLocalFunction( client.registerLocalFunction(
name = "exit_navigation", name = "exit_navigation",
description = "结束当前导航服务", description = "结束当前导航服务",

190
local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

@ -8,6 +8,7 @@ import user_portrait // 添加这行
import MediaPlayer import MediaPlayer
import AVFoundation import AVFoundation
import UserNotifications
// 在文件顶部添加通知名称定义 // 在文件顶部添加通知名称定义
extension Notification.Name { extension Notification.Name {
@ -64,9 +65,11 @@ public class SystemFunctionHandler: NSObject {
do { do {
// 注册退出交互函数 // 注册退出交互函数
registerExitInteractionFunction(client: client) registerExitInteractionFunction(client: client)
// 注册退出导航模式
registerExitNavigationFunction(client: client)
// 注册翻译模式函数 // 注册翻译模式函数
registerTranslationModeFunction(client: client) // registerTranslationModeFunction(client: client)
// 注册发送短信函数 // 注册发送短信函数
registerSendTextMessageFunction(client: client) registerSendTextMessageFunction(client: client)
@ -119,7 +122,22 @@ public class SystemFunctionHandler: NSObject {
handler: handler handler: handler
) )
} }
/// 注册退出交互函数
private func registerExitNavigationFunction(client: MCPClient) {
let parameters: [String: Any] = [
"type": "object",
"properties": [:],
"required": []
]
let handler = ExitNavigationHandler()
_ = client.registerLocalFunction(
name: "exit_navigation",
description: "结束当前导航",
parameters: parameters,
handler: handler
)
}
/// 注册翻译模式函数 /// 注册翻译模式函数
private func registerTranslationModeFunction(client: MCPClient) { private func registerTranslationModeFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
@ -558,7 +576,13 @@ private class ExitInteractionHandler: FunctionHandler {
return "{\"result\": \"已结束当前交互\"}" return "{\"result\": \"已结束当前交互\"}"
} }
} }
/// 退出交互处理器
private class ExitNavigationHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String {
NotificationCenter.default.post(name: NSNotification.Name("ExitNavigation"), object: nil)
return "{\"result\": \"已结束当前导航\"}"
}
}
/// 翻译模式处理器 /// 翻译模式处理器
private class TranslationModeHandler: FunctionHandler { private class TranslationModeHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
@ -670,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 {
@ -683,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)")
} }
} }
} }

92
local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift

@ -1,5 +1,6 @@
import Foundation import Foundation
import AVFoundation import AVFoundation
import CoreBluetooth
/// 蓝牙事件类型 /// 蓝牙事件类型
enum BluetoothEventType { enum BluetoothEventType {
@ -18,32 +19,49 @@ enum BluetoothState: String {
} }
/// 简化的蓝牙助手类,专注于iOS上的蓝牙耳机检测 /// 简化的蓝牙助手类,专注于iOS上的蓝牙耳机检测
class ClassicBluetoothHelper: NSObject { class ClassicBluetoothHelper: NSObject, CBCentralManagerDelegate {
// 状态监控相关 private var centralManager: CBCentralManager?
private var isMonitoring = false private var bluetoothState: CBManagerState = .unknown
// 添加缺少的属性声明
private var stateCallback: ((BluetoothEventType, [String: Any]) -> Void)? private var stateCallback: ((BluetoothEventType, [String: Any]) -> Void)?
private var isMonitoring: Bool = false
// 音频路由变化观察者 // 音频路由变化观察者
private var routeChangeObserver: NSObjectProtocol? private var routeChangeObserver: NSObjectProtocol?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
deinit { deinit {
stopBluetoothStateMonitoring() stopBluetoothStateMonitoring()
} }
/// 检查蓝牙是否启用(通过检查是否有连接的蓝牙音频设备) func centralManagerDidUpdateState(_ central: CBCentralManager) {
func isBluetoothEnabled() -> Bool { bluetoothState = central.state
// 检查是否有连接的蓝牙音频设备 print("lxm---蓝牙状态更新: \(bluetoothState.rawValue)")
let hasBtDevices = !getConnectedAudioDevices().isEmpty
// 如果有连接的蓝牙设备,则认为蓝牙是开启状态 // 向Flutter端报告蓝牙状态变化
if hasBtDevices { if isMonitoring {
print("蓝牙状态检查: 已检测到蓝牙设备,蓝牙已开启") let currentState = getBluetoothAvailability()
reportBluetoothState(currentState)
}
}
func isBluetoothEnabled() -> Bool {
switch bluetoothState {
case .poweredOn:
return true return true
case .poweredOff, .unsupported, .unauthorized, .resetting:
return false
case .unknown:
// 如果状态未知,回退到原来的音频设备检测方法
return !getConnectedAudioDevices().isEmpty
@unknown default:
return false
} }
// 如果没有连接设备,返回false (无法100%确定蓝牙是否打开,只能知道没有连接设备)
print("蓝牙状态检查: 未检测到蓝牙设备")
return false
} }
/// 获取已连接的蓝牙耳机 /// 获取已连接的蓝牙耳机
@ -206,19 +224,47 @@ class ClassicBluetoothHelper: NSObject {
print("关闭音频会话失败: \(error.localizedDescription)") print("关闭音频会话失败: \(error.localizedDescription)")
} }
} }
/// 获取蓝牙可用性状态(优化后的统一状态判断方法)
func getBluetoothAvailability() -> BluetoothState {
switch bluetoothState {
case .poweredOn:
return .on
case .poweredOff:
return .off
case .unauthorized:
return .unauthorized
case .unsupported:
return .unsupported
case .unknown, .resetting:
// 当CoreBluetooth状态未知时,回退到音频设备检测
return getConnectedAudioDevices().isEmpty ? .unknown : .on
@unknown default:
return .unknown
}
}
/// 报告当前蓝牙状态 /// 报告当前蓝牙状态(优化后使用统一的状态判断方法)
func reportCurrentBluetoothState() { func reportCurrentBluetoothState() {
// 检查当前是否有蓝牙设备连接 // 使用优化后的统一状态判断方法
let devices = getConnectedAudioDevices() let currentState = getBluetoothAvailability()
if !devices.isEmpty { // 根据状态输出相应的日志信息
print("蓝牙状态: 已检测到蓝牙设备,报告状态为开启") switch currentState {
reportBluetoothState(.on) case .on:
} else { print("蓝牙状态: 蓝牙已开启")
print("蓝牙状态: 未检测到蓝牙设备,报告状态为未知") case .off:
reportBluetoothState(.unknown) print("蓝牙状态: 蓝牙已关闭")
case .unauthorized:
print("蓝牙状态: 应用未授权使用蓝牙")
case .unsupported:
print("蓝牙状态: 设备不支持蓝牙")
case .unknown:
print("蓝牙状态: 蓝牙状态未知")
} }
// 向Flutter端报告状态
reportBluetoothState(currentState)
} }
// 报告蓝牙状态变化 // 报告蓝牙状态变化

Loading…
Cancel
Save