diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index cc533b2d6..000000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,201 +0,0 @@ -PODS: - - amap_service (0.0.1): - - AMapNavi (~> 10.0.900) - - Flutter - - AMapFoundation (1.8.2) - - AMapNavi (10.0.900): - - AMapFoundation (>= 1.8.2) - - audio_waveforms (0.0.1): - - Flutter - - classic_bluetooth (0.0.1): - - Flutter - - FBAEMKit (18.0.0): - - FBSDKCoreKit_Basics (= 18.0.0) - - FBSDKCoreKit (18.0.0): - - FBAEMKit (= 18.0.0) - - FBSDKCoreKit_Basics (= 18.0.0) - - FBSDKCoreKit_Basics (18.0.0) - - FBSDKLoginKit (18.0.0): - - FBSDKCoreKit (= 18.0.0) - - Flutter (1.0.0) - - flutter_facebook_auth (7.1.2): - - FBSDKLoginKit (~> 18.0.0) - - Flutter - - flutter_image_compress_common (1.0.0): - - Flutter - - Mantle - - SDWebImage - - SDWebImageWebPCoder - - flutter_local_notifications (0.0.1): - - Flutter - - flutter_secure_storage (6.0.0): - - Flutter - - flutter_tts (0.0.1): - - Flutter - - fluwx (0.0.1): - - Flutter - - fluwx/pay (= 0.0.1) - - fluwx/pay (0.0.1): - - Flutter - - WechatOpenSDK-XCFramework (~> 2.0.4) - - haptic_feedback (0.5.1): - - Flutter - - keyboard_height_plugin (0.0.1): - - Flutter - - libwebp (1.5.0): - - libwebp/demux (= 1.5.0) - - libwebp/mux (= 1.5.0) - - libwebp/sharpyuv (= 1.5.0) - - libwebp/webp (= 1.5.0) - - libwebp/demux (1.5.0): - - libwebp/webp - - libwebp/mux (1.5.0): - - libwebp/demux - - libwebp/sharpyuv (1.5.0) - - libwebp/webp (1.5.0): - - libwebp/sharpyuv - - Mantle (2.2.0): - - Mantle/extobjc (= 2.2.0) - - Mantle/extobjc (2.2.0) - - ota (0.0.1): - - Flutter - - permission_handler_apple (9.3.0): - - Flutter - - QCloudCore (6.4.7): - - QCloudCore/Default (= 6.4.7) - - QCloudCore/Default (6.4.7): - - QCloudTrack/Beacon (= 6.4.7) - - QCloudCOSXML (6.4.7): - - QCloudCOSXML/Default (= 6.4.7) - - QCloudCOSXML/Default (6.4.7): - - QCloudCore (= 6.4.7) - - QCloudTrack/Beacon (6.4.7) - - record_darwin (1.0.0): - - Flutter - - SDWebImage (5.21.0): - - SDWebImage/Core (= 5.21.0) - - SDWebImage/Core (5.21.0) - - SDWebImageWebPCoder (0.14.6): - - libwebp (~> 1.0) - - SDWebImage/Core (~> 5.17) - - spotify_sdk (0.0.1): - - Flutter - - tencentcloud_cos_sdk_plugin (1.2.3): - - Flutter - - QCloudCOSXML (= 6.4.7) - - WechatOpenSDK-XCFramework (2.0.4) - - wifi_iot (0.0.1): - - Flutter - -DEPENDENCIES: - - amap_service (from `.symlinks/plugins/amap_service/ios`) - - audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`) - - classic_bluetooth (from `.symlinks/plugins/classic_bluetooth/ios`) - - Flutter (from `Flutter`) - - flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`) - - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) - - fluwx (from `.symlinks/plugins/fluwx/ios`) - - haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`) - - keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`) - - ota (from `.symlinks/plugins/ota/ios`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - record_darwin (from `.symlinks/plugins/record_darwin/ios`) - - spotify_sdk (from `.symlinks/plugins/spotify_sdk/ios`) - - tencentcloud_cos_sdk_plugin (from `.symlinks/plugins/tencentcloud_cos_sdk_plugin/ios`) - - wifi_iot (from `.symlinks/plugins/wifi_iot/ios`) - -SPEC REPOS: - trunk: - - AMapFoundation - - AMapNavi - - FBAEMKit - - FBSDKCoreKit - - FBSDKCoreKit_Basics - - FBSDKLoginKit - - libwebp - - Mantle - - QCloudCore - - QCloudCOSXML - - QCloudTrack - - SDWebImage - - SDWebImageWebPCoder - - WechatOpenSDK-XCFramework - -EXTERNAL SOURCES: - amap_service: - :path: ".symlinks/plugins/amap_service/ios" - audio_waveforms: - :path: ".symlinks/plugins/audio_waveforms/ios" - classic_bluetooth: - :path: ".symlinks/plugins/classic_bluetooth/ios" - Flutter: - :path: Flutter - flutter_facebook_auth: - :path: ".symlinks/plugins/flutter_facebook_auth/ios" - flutter_image_compress_common: - :path: ".symlinks/plugins/flutter_image_compress_common/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_secure_storage: - :path: ".symlinks/plugins/flutter_secure_storage/ios" - flutter_tts: - :path: ".symlinks/plugins/flutter_tts/ios" - fluwx: - :path: ".symlinks/plugins/fluwx/ios" - haptic_feedback: - :path: ".symlinks/plugins/haptic_feedback/ios" - keyboard_height_plugin: - :path: ".symlinks/plugins/keyboard_height_plugin/ios" - ota: - :path: ".symlinks/plugins/ota/ios" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" - record_darwin: - :path: ".symlinks/plugins/record_darwin/ios" - spotify_sdk: - :path: ".symlinks/plugins/spotify_sdk/ios" - tencentcloud_cos_sdk_plugin: - :path: ".symlinks/plugins/tencentcloud_cos_sdk_plugin/ios" - wifi_iot: - :path: ".symlinks/plugins/wifi_iot/ios" - -SPEC CHECKSUMS: - amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b - AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec - AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd - audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf - classic_bluetooth: 29f392f1ab7c52862a9ef036c94f2713b02e0c50 - FBAEMKit: e34530df538b8eb8aeb53c35867715ba6c63ef0c - FBSDKCoreKit: d3f479a69127acebb1c6aad91c1a33907bcf6c2f - FBSDKCoreKit_Basics: 017b6dc2a1862024815a8229e75661e627ac1e29 - FBSDKLoginKit: 5875762d1fe09ddcb05d03365d4f5dc34413843d - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_facebook_auth: d97dc09216aebd99d7ac52360186a630f67bdf18 - flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 - flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4 - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833 - fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b - haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0 - keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05 - libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - ota: 20c3e522c57e7ba70ce118109e73650acc9fec76 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - QCloudCore: 49e7571984a8e5fd8f0fd58c2f3b806255796223 - QCloudCOSXML: 7205e76aa9cf613468222615483c9d7c074e4298 - QCloudTrack: 3b53a7fc4fe3920e407f2aa73f2452992a61f7f3 - record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a - SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 - SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 - spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca - tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b - WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f - wifi_iot: f645260a2be8608517b2a9bf4c39b98e97003acc - -PODFILE CHECKSUM: e69e629768e642fde97676ea149350c61d57751e - -COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a96a23c4c..c91c43c66 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -252,7 +252,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, D013B7972DFA83E400B2B69B /* XCRemoteSwiftPackageReference "flutterfire" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -511,6 +511,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = NO; + NEW_SETTING = ""; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -649,6 +650,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = YES; + NEW_SETTING = ""; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -701,6 +703,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; MTL_ENABLE_DEBUG_INFO = NO; + NEW_SETTING = ""; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -718,7 +721,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 537SFY28XS; + DEVELOPMENT_TEAM = K39FB49XKY; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Deep Voice"; @@ -797,7 +800,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index 0301daa87..08733ee66 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -146,10 +146,10 @@ class Api { } //解绑定设备 - static unbindearphone([params]) { + static unbinddevice([params]) { return DioManager().request( NWMethod.post, - '/api/home/user_unbindearphone', + '/api/home/user_unbinddevice', params: params, ); } diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart index 6121ed7f8..1a1fe15a8 100644 --- a/lib/modules/devices/controllers/devices_controller.dart +++ b/lib/modules/devices/controllers/devices_controller.dart @@ -35,7 +35,7 @@ class DevicesController extends GetxController { } void unbindearphone(UserDevice device) async { - final response = await Api.unbindearphone({ + final response = await Api.unbinddevice({ "id": device.id, }); if (response != null) { diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 753dd87c4..29dd1d360 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -464,6 +464,7 @@ audioStream.saveAudioDataTo(data: audioData) os_log("中断当前AI流式响应", log: logger, type: .info) _ = interruptCurrentResponse() } + stopTts(); isAiStreaming = true os_log("设置AI流式状态为true", log: logger, type: .info) @@ -689,7 +690,6 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) - notreply = _notreply } metadata["notreply"] = notreply - // 处理卡片数据 if let cardMusic = jsonDict["card_music"] as? [String: Any] { processMusicCard(cardMusic) @@ -958,6 +958,7 @@ class ChatApiStreamCallback: StreamCallback { } func onToken(_ sessionId:String,_ token: String) { do { +// print("liwei--------------- AI Call onToken! sessionId:\(sessionId) token:\(token)") guard let agentService = try agentService else { return } if (sessionId != agentService.currsessionId) { return @@ -1044,18 +1045,12 @@ class ChatApiStreamCallback: StreamCallback { let name = functionCall["name"] as? String ?? "" os_log("收到函数调用: %{public}@", log: agentService.logger, type: .info, name) print("onFunctionCall=functionCall\(functionCall)") - // try agentService.audioPlayer?.playCallingSound() - + try agentService.sendEvent(name: "function_call", data: [ "name": name, "arguments": functionCall ]) - - if name == "exit_interaction" { - os_log("执行退出交互", log: agentService.logger, type: .info) - agentService.stopRecognition() - } - + }catch{ print("liwei--------------- AI Call onFunctionCall 异常: \(error)") } @@ -1067,20 +1062,22 @@ class ChatApiStreamCallback: StreamCallback { guard let agentService = agentService else { return } let name = functionCall["name"] as? String ?? "" - os_log("收到函数调用结果: %{public}@", log: agentService.logger, type: .info, functionCallResult) + // agentService.audioPlayer?.stopCallingSound() let resultmap = agentService.autoHandleFunctionCallResult(toolName:name,functionCallResult:functionCallResult) metadata = resultmap["meta"] as? String ?? "" broadcast = resultmap["broadcast"] as? Bool ?? true reply = !(resultmap["notreply"] as? Bool ?? true) - os_log("卡片数据: %{public}@", log: agentService.logger, type: .info, metadata) agentService.sendEvent(name: "function_call_result", data: [ "function_call": functionCall, "result": functionCallResult, "meta":metadata, ]) - os_log("处理函数调用结果完成", log: agentService.logger, type: .info) + os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@", + log: agentService.logger, + type: .info, + "\(broadcast)", "\(reply)") }catch{ print("liwei--------------- AI Call onFunctionCallResult 异常: \(error)") } diff --git a/local_plugins/amap_service/ios/Classes/AmapService.swift b/local_plugins/amap_service/ios/Classes/AmapService.swift index 30f6d1308..c962cc002 100644 --- a/local_plugins/amap_service/ios/Classes/AmapService.swift +++ b/local_plugins/amap_service/ios/Classes/AmapService.swift @@ -24,7 +24,7 @@ public class AmapService: NSObject, AMapNaviDriveManagerDelegate { // 设置高德地图服务 // 1. 设置隐私合规 AMapServices.shared()?.enableHTTPS = true - AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03" + AMapServices.shared()?.apiKey = "0b704e172b7ade5a7a18728e8f517a05" } public func startNavigation(from start: String, to end: String) { diff --git a/local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift b/local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift index 05e7c0ec4..4be2e55a8 100644 --- a/local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift +++ b/local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift @@ -22,7 +22,7 @@ public class AmapServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { // 高德SDK初始化 DispatchQueue.main.async { - AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03" + AMapServices.shared()?.apiKey = "0b704e172b7ade5a7a18728e8f517a05" AMapServices.shared()?.enableHTTPS = true if #available(iOS 13.0, *) { @@ -101,4 +101,4 @@ public class AmapServicePlugin: NSObject, FlutterPlugin, FlutterStreamHandler { result(FlutterMethodNotImplemented) } } -} \ No newline at end of file +} diff --git a/local_plugins/amap_service/ios/amap_service.podspec b/local_plugins/amap_service/ios/amap_service.podspec index 5e90480f9..b33af0daa 100644 --- a/local_plugins/amap_service/ios/amap_service.podspec +++ b/local_plugins/amap_service/ios/amap_service.podspec @@ -22,6 +22,6 @@ A new Flutter project. 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.platform = :ios, '12.0' + s.platform = :ios, '16.0' s.swift_version = '5.0' end diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index e45b51003..24b60e444 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -576,7 +576,7 @@ public class AzureAsrHelper: NSObject { guard let self = self else { return } while self.isRunning { - print("是否写入: \(self.isWriting)") + // print("是否写入: \(self.isWriting)") if !self.isWriting { usleep(10_000) continue diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift index 41d31caa7..b0c76fa81 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift @@ -24,6 +24,12 @@ class BleConst { /** 客户端特征配置描述符UUID */ static let CLIENT_CHAR_CONFIG_UUID = CBUUID(string: "00002902-0000-1000-8000-00805f9b34fb") + /** 扫描服务UUID - 用于后台扫描过滤 */ + static let SERVICE_UUID = CBUUID(string: "abc0") + + /** 扫描服务UUID1 - 用于后台扫描过滤 */ + static let SERVICE_UUID1 = CBUUID(string: "fd7b") + /** BLE扫描Action */ static let ACTION_SCAN_RESULT = "com.yunqiinnovation.ble_service.ACTION_BLE_SCAN" @@ -90,4 +96,4 @@ class BleConst { static let AUDIO_CHANNEL_RIGHT: UInt8 = 0x02 /** 立体声 */ static let AUDIO_CHANNEL_STEREO: UInt8 = 0x03 -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift index 6647a5e4a..66b8d73e2 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift @@ -238,11 +238,7 @@ private var cmdReplyType: UInt8 = 0 manager.scanForPeripherals(withServices: nil, options: [ CBCentralManagerScanOptionAllowDuplicatesKey: true // 允许重复以获取更新的广播数据 ]) - // let serviceUUIDs = [BleConst.SERVICE_UUID] - // manager.scanForPeripherals(withServices: serviceUUIDs, options: [ - // CBCentralManagerScanOptionAllowDuplicatesKey: true // 允许重复以获取更新的广播数据 - // ]) - // os_log("🔍 开始扫描所有BLE设备(无过滤),超时时间: %.1f秒", log: logger, type: .info, timeout) + os_log("🔍 开始扫描所有BLE设备(无过滤),超时时间: %.1f秒", log: logger, type: .info, timeout) // 设置扫描超时 scanTimer?.invalidate() @@ -391,6 +387,7 @@ private var cmdReplyType: UInt8 = 0 // 遍历所有已发现的设备,查找匹配的设备名 for (deviceId, deviceInfo) in discoveredDevices { + print("deviceInfo: \(deviceInfo)") if let name = deviceInfo["name"] as? String, name == deviceName { // 获取对应的广播数据 var deviceData = deviceInfo @@ -1224,14 +1221,76 @@ extension BleService: CBCentralManagerDelegate { os_log("📱 设备RSSI变化较大 - 名称: %{public}@, UUID: %{public}@, RSSI: %d → %d", log: logger, type: .debug, deviceName, deviceId, oldRssi, newRssi) } } else { - // 新设备,需要上报 + // 新设备,需要上报并打印详细信息 shouldNotify = true - os_log("📱 发现新设备 - 名称: %{public}@, UUID: %{public}@", log: logger, type: .info, deviceName, deviceId) - // 打印 hex 数据 - 直接从 deviceInfo 获取 - if let hex = deviceInfo["hex"] as? String { - os_log("📡 设备 hex 数据: %{public}@", log: logger, type: .info, hex) + // 打印详细的设备信息 + os_log("📱 ===== 发现新BLE设备 =====", log: logger, type: .info) + os_log("📱 设备名称: %{public}@", log: logger, type: .info, deviceName) + os_log("📱 设备UUID(MAC地址): %{public}@", log: logger, type: .info, deviceId) + os_log("📱 RSSI信号强度: %d dBm", log: logger, type: .info, RSSI.intValue) + os_log("📱 发现时间: %{public}@", log: logger, type: .info, Date().description) + + // 打印服务UUID信息 + if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { + var uuidDetails: [String] = [] + for uuid in serviceUUIDs { + let uuidString = uuid.uuidString + // 检查是否为16位UUID(通常显示为4个字符) + if uuidString.count == 4 { + let fullUUID = "0000\(uuidString)-0000-1000-8000-00805F9B34FB" + uuidDetails.append("\(uuidString) (完整: \(fullUUID))") + } else { + uuidDetails.append(uuidString) + } + } + os_log("📱 服务UUIDs: %{public}@", log: logger, type: .info, uuidDetails.joined(separator: ", ")) + os_log("📱 服务UUID数量: %d", log: logger, type: .info, serviceUUIDs.count) + } else { + os_log("📱 服务UUIDs: 无", log: logger, type: .info) + } + + // 打印制造商数据 + if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { + let hexString = manufacturerData.map { String(format: "%02X", $0) }.joined(separator: " ") + os_log("📱 制造商数据: %{public}@", log: logger, type: .info, hexString) + } else { + os_log("📱 制造商数据: 无", log: logger, type: .info) + } + + // 打印服务数据 + if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] { + for (serviceUUID, data) in serviceData { + let hexString = data.map { String(format: "%02X", $0) }.joined(separator: " ") + os_log("📱 服务数据[%{public}@]: %{public}@", log: logger, type: .info, serviceUUID.uuidString, hexString) + } + } else { + os_log("📱 服务数据: 无", log: logger, type: .info) + } + + // 打印本地名称 + if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String { + os_log("📱 本地名称: %{public}@", log: logger, type: .info, localName) + } + + // 打印TX功率等级 + if let txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber { + os_log("📱 TX功率等级: %d dBm", log: logger, type: .info, txPowerLevel.intValue) } + + // 打印是否可连接 + if let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber { + os_log("📱 可连接状态: %{public}@", log: logger, type: .info, isConnectable.boolValue ? "是" : "否") + } + + // 打印完整的原始广播数据(十六进制) + if let hex = deviceInfo["hex"] as? String, !hex.isEmpty { + os_log("📱 原始广播数据(HEX): %{public}@", log: logger, type: .info, hex) + } else { + os_log("📱 原始广播数据(HEX): 无", log: logger, type: .info) + } + + os_log("📱 =========================", log: logger, type: .info) } // 更新缓存(无论是否上报都要更新) diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift index ad6b53219..cdf5cfa3a 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift @@ -67,12 +67,24 @@ public class ChatApiService: NSObject { // MARK: - 公共方法 /// 设置流式回调 - public func setStreamCallback(_ sessionId:String,_ callback: StreamCallback) { + public func setStreamCallback(_ sessionId: String, _ callback: StreamCallback) { + print("[ChatApiService] 设置回调 for sessionId: \(sessionId)") + print("[ChatApiService] 回调对象: \(callback)") self.sessionCallbacks[sessionId] = callback + print("[ChatApiService] 回调设置完成,当前回调数量: \(sessionCallbacks.count)") } - public func getSessionCallback(_ sessionId:String)->StreamCallback?{ - return self.sessionCallbacks[sessionId] + public func getSessionCallback(_ sessionId: String) -> StreamCallback? { + let callback = self.sessionCallbacks[sessionId] + print("[ChatApiService] getSessionCallback for sessionId: \(sessionId)") + print("[ChatApiService] sessionCallbacks count: \(sessionCallbacks.count)") + print("[ChatApiService] sessionCallbacks keys: \(Array(sessionCallbacks.keys))") + print("[ChatApiService] callback exists: \(callback != nil)") + return callback + } + //移除回调函数 + public func removeSessionCallback(_ sessionId: String){ + self.sessionCallbacks.removeValue(forKey: sessionId) } /// 初始化ChatAPI服务 @@ -289,6 +301,7 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) { let query: ChatQuery if !tools.isEmpty { let chatTools = self.convertToOpenAITools(tools) + query = ChatQuery( messages: chatMessages, model: currentModel, @@ -296,6 +309,8 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) { temperature: 0.7, tools: chatTools ) + + print("[ChatApiService] 发起AI问答 问题:\(messages[0]) 工具数量: \(chatTools.count)") } else { query = ChatQuery( messages: chatMessages, @@ -319,7 +334,17 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) { // 处理文本内容 if let content = delta.content { replyContext += content - self.getSessionCallback(sessionId)?.onToken(sessionId, content) + print("[ChatApiService] 收到内容: \(content)") + print("[ChatApiService] 当前sessionId: \(sessionId)") + + let callback = self.getSessionCallback(sessionId) + if let callback = callback { + print("[ChatApiService] 找到回调,准备调用 onToken") + callback.onToken(sessionId, content) + print("[ChatApiService] onToken 调用完成") + } else { + print("[ChatApiService] 警告:未找到 sessionId \(sessionId) 的回调") + } } // 收集工具调用信息 @@ -690,11 +715,10 @@ private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] { currentStreamTask = nil // 2. 通知旧会话被中止 - getSessionCallback(oldSessionId)?.onComplete(currSessionId) +// getSessionCallback(oldSessionId)?.onComplete(currSessionId) // 3. 清理状态 currSessionId = "" - sessionCallbacks.removeValue(forKey: oldSessionId) toolCalls.removeAll() } } @@ -703,7 +727,7 @@ private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] { private func clearCurrentSession() { let oldSessionId = currSessionId currSessionId = "" - sessionCallbacks.removeValue(forKey: oldSessionId) + self.sessionCallbacks.removeValue(forKey: oldSessionId) currentStreamTask = nil toolCalls.removeAll() } @@ -876,14 +900,17 @@ private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] { var chatTools: [ChatQuery.ChatCompletionToolParam] = [] for tool in tools { + guard let type = tool["type"] as? String, type == "function", let function = tool["function"] as? [String: Any], let name = function["name"] as? String, let description = function["description"] as? String, let parameters: [String: Any] = function["parameters"] as? [String: Any] else { + print("无法将 parameters 转换为 JSON 数据") continue } + // 将字典转换为 JSON 数据 guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { print("无法将 parameters 转换为 JSON 数据") @@ -904,6 +931,7 @@ private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] { // 创建工具参数 let toolParam = ChatQuery.ChatCompletionToolParam(function: functionDefinition) + print("解析工具 \(name):\(schema)") chatTools.append(toolParam) } @@ -922,4 +950,4 @@ private class LocalFunctionHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { return "LOCAL_FUNCTION:\(functionName)" } -} +} diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/CustomSseClientTransport.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/CustomSseClientTransport.swift index 2b9f58eb6..e5da6b689 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/CustomSseClientTransport.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/CustomSseClientTransport.swift @@ -17,6 +17,9 @@ public actor CustomSseClientTransport: Transport { private let reconnectionTime: TimeInterval private let session: URLSession + // 断线回调 + private let onConnectionLost: (() -> Void)? + // URL解析结果 private var hostPart: String = "" private var pathPart: String = "" @@ -26,6 +29,7 @@ public actor CustomSseClientTransport: Transport { // 连接状态 private var isConnected = false private var isInitialized = false + private var connectionMonitorTask: Task? // 消息流 private let messageStream: AsyncThrowingStream @@ -57,11 +61,13 @@ public actor CustomSseClientTransport: Transport { urlString: String, reconnectionTime: TimeInterval = 1.0, session: URLSession = .shared, - logger: Logger? = nil + logger: Logger? = nil, + onConnectionLost: (() -> Void)? = nil ) { self.originalUrlString = urlString self.reconnectionTime = reconnectionTime self.session = session + self.onConnectionLost = onConnectionLost self.logger = logger ?? Logger( label: "com.yunqiinnovation.chat_api.CustomSseClientTransport", factory: { _ in SwiftLogNoOpLogHandler() } @@ -93,6 +99,9 @@ public actor CustomSseClientTransport: Transport { _ = try await waitForEndpoint() isConnected = true + + // 启动连接监控 + startConnectionMonitoring() } /// 断开传输连接 @@ -104,6 +113,10 @@ public actor CustomSseClientTransport: Transport { isConnected = false isInitialized = false + // 停止连接监控 + connectionMonitorTask?.cancel() + connectionMonitorTask = nil + // 取消SSE任务 sseTask?.cancel() sseTask = nil @@ -120,6 +133,11 @@ public actor CustomSseClientTransport: Transport { endpointContinuation = nil } + /// 检查连接是否活跃 + public func isConnectionActive() -> Bool { + return isConnected && isInitialized && eventSource != nil + } + /// 发送数据 public func send(_ data: Data) async throws { guard isConnected, let endpointUrl = endpointUrl else { @@ -329,10 +347,17 @@ public actor CustomSseClientTransport: Transport { /// 处理SSE错误 private func handleSseError(_ error: String) { + logger.error("SSE错误: \(error)") + let mcpError = MCPError.internalError("SSE error: \(error)") endpointContinuation?.resume(throwing: mcpError) endpointContinuation = nil messageContinuation.finish(throwing: mcpError) + + // 触发连接断开处理 + Task { + await handleConnectionLost() + } } /// 处理SSE连接打开 @@ -382,4 +407,73 @@ public actor CustomSseClientTransport: Transport { private func handleOtherEvent(_ data: String) { safeParseMessage(data) } -} \ No newline at end of file + + // MARK: - 连接监控和错误处理 + + /// 启动连接监控 + private func startConnectionMonitoring() { + connectionMonitorTask = Task { [weak self] in + await self?.monitorConnectionHealth() + } + } + + /// 监控连接健康状态 + private func monitorConnectionHealth() async { + while isInitialized && !Task.isCancelled { + // 检查连接状态 + if !isConnectionActive() { + await handleConnectionLost() + break + } + + // 每5秒检查一次连接状态 + try? await Task.sleep(nanoseconds: 5_000_000_000) + } + } + + /// 处理连接丢失 - 简化版本,只重置状态 + private func handleConnectionLost() async { + guard isConnected else { return } + + logger.warning("SSE连接丢失,重置连接状态") + + // 重置连接状态 + await resetConnectionState() + + // 通知外部连接丢失 + onConnectionLost?() + } + + /// 重置连接状态 + private func resetConnectionState() async { + // 标记连接已断开 + isConnected = false + + // 停止连接监控 + connectionMonitorTask?.cancel() + connectionMonitorTask = nil + + // 清理连接资源 + await cleanupConnection() + + // 重置端点信息 + endpointUrl = nil + + // 如果有等待中的端点请求,取消它 + endpointContinuation?.resume(throwing: MCPError.internalError("Connection lost")) + endpointContinuation = nil + + logger.info("连接状态已重置,等待下次工具调用时重连") + } + + /// 清理连接资源 + private func cleanupConnection() async { + // 取消SSE任务 + sseTask?.cancel() + sseTask = nil + + // 关闭EventSource + await eventSource?.close() + eventSource = nil + } +} diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift index aba509fc5..b42708dca 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift @@ -39,38 +39,71 @@ public class MCPSubClient { private var toolMaps: [[String: Any]] = [] private var isConnectedFlag = false - public init(serverId: String, serverUrl: String,filterTools:String) { + // 重连配置 + private let maxRetryAttempts = 3 + private let initialReconnectDelay: TimeInterval = 1.0 + private let maxReconnectDelay: TimeInterval = 30.0 + private var currentReconnectDelay: TimeInterval = 1.0 + private var retryCount = 0 + private var reconnectTask: Task? + + // 连接状态锁 + private let connectionLock = NSLock() + + public init(serverId: String, serverUrl: String, filterTools: String) { self.serverId = serverId self.serverUrl = serverUrl self.filterTools = filterTools + self.currentReconnectDelay = initialReconnectDelay } public func connect() async -> Bool { + return await connectionLock.withLock { + await performConnect() + } + } + + private func performConnect() async -> Bool { + if isConnectedFlag { + return true + } + do { // 1. 创建MCP客户端 - mcpClient = Client(name: "deep_voice_\(serverId)", version: "1.0.0") + self.mcpClient = Client(name: "deep_voice_\(serverId)", version: "1.0.0") - // 2. 创建自定义SSE传输层 + // 2. 创建自定义SSE传输层,添加断线回调 let swiftLogger = Logger(label: "mcp.transport.custom_sse.\(serverId)") let transport = CustomSseClientTransport( urlString: serverUrl, reconnectionTime: 1.0, - logger: swiftLogger + logger: swiftLogger, + onConnectionLost: { [weak self] in + Task { + await self?.handleConnectionLost() + } + } ) - // 3. 连接到服务器 - _ = try await mcpClient!.connect(transport: transport) + // 3. 连接到服务器(添加超时) + try await withTimeout(seconds: 10) { + try await self.mcpClient!.connect(transport: transport) + } // 4. 获取工具列表 - let (toolList, _) = try await mcpClient!.listTools() + let (toolList, _) = try await self.mcpClient!.listTools() // 5. 处理工具列表 processTools(toolList) isConnectedFlag = true + retryCount = 0 // 重置重试计数 + currentReconnectDelay = initialReconnectDelay // 重置延迟 + return true } catch { + await handleConnectionError(error as! Error) return false } } @@ -130,7 +163,32 @@ public class MCPSubClient { ] } + /// 检查连接状态 + /// 检查连接状态 + public func checkConnection() async -> Bool { + if !isConnectedFlag { + return await connect() + } + + // 检查传输层连接状态 + // 这里可以添加ping测试或其他连接验证 + + return isConnectedFlag + } + + /// 调用工具前检查连接 public func callTool(name: String, arguments: [String: Any]) async -> [String: Any]? { + // 先检查连接状态 + if !(await checkConnection()) { + return [ + "content": [[ + "type": "text", + "text": "Tool call failed: mcp: service not connected" + ]], + "isError": true + ] + } + guard isConnectedFlag, let client = mcpClient else { return createErrorResult("Not connected to MCP server") } @@ -227,6 +285,74 @@ public class MCPSubClient { } isConnectedFlag = false } + + /// 处理连接断开 + private func handleConnectionLost() async { + await connectionLock.withLock { + if isConnectedFlag { + print("[\(serverId)] 连接已断开,更新状态") + isConnectedFlag = false + + // 启动重连逻辑 + startReconnection() + } + } + } + + /// 启动重连 + private func startReconnection() { + // 取消之前的重连任务 + reconnectTask?.cancel() + + reconnectTask = Task { [weak self] in + await self?.performReconnection() + } + } + + /// 执行重连 + private func performReconnection() async { + while retryCount < maxRetryAttempts && !Task.isCancelled { + retryCount += 1 + + print("[\(serverId)] 尝试重连 (\(retryCount)/\(maxRetryAttempts)),延迟 \(currentReconnectDelay)秒") + + try? await Task.sleep(for: .seconds(currentReconnectDelay)) + + if await connect() { + print("[\(serverId)] 重连成功") + return + } + + // 指数退避 + currentReconnectDelay = min(currentReconnectDelay * 2, maxReconnectDelay) + } + + if retryCount >= maxRetryAttempts { + print("[\(serverId)] 已达到最大重试次数,停止重连") + } + } + + /// 处理连接错误 + private func handleConnectionError(_ error: Error) async { + print("[\(serverId)] 连接错误: \(error.localizedDescription)") + isConnectedFlag = false + + // 如果是MCP特定错误,可以进行特殊处理 + if let mcpError = error as? MCPError { + // 处理MCP特定错误 + print("[\(serverId)] MCP错误类型: \(mcpError)") + } + + // 如果还有重试机会,启动重连 + // if retryCount < maxRetryAttempts { + // startReconnection() + // } + } + + /// 获取连接状态 + public func getConnectionStatus() -> Bool { + return isConnectedFlag + } } /// MCP客户端 @@ -285,6 +411,25 @@ public class MCPClient { } } + // 添加连接成功后的工具列表汇总输出 + if connectedCount > 0 { + print("[MCPClient] 🎉 MCP 连接完成,共连接 \(connectedCount) 个服务器") + print("[MCPClient] 📋 所有可用工具汇总:") + + let allTools = getToolMaps() + for (index, toolMap) in allTools.enumerated() { + if let function = toolMap["function"] as? [String: Any], + let name = function["name"] as? String, + let description = function["description"] as? String { + print("[MCPClient] \(index + 1). \(name) - \(description)") + } + } + + print("[MCPClient] 📊 工具总数: \(allTools.count)") + print("[MCPClient] 🔧 本地函数: \(localFunctionDefs.count) 个") + print("[MCPClient] 🌐 远程工具: \(allTools.count - localFunctionDefs.count) 个") + } + return connectedCount > 0 } @@ -298,6 +443,7 @@ public class MCPClient { parameters: Any, handler: FunctionHandler ) -> Bool { + print("[MCPClient] 注册本地函数: \(name)") localFunctions[name] = handler let functionDef: [String: Any] = [ @@ -307,6 +453,9 @@ public class MCPClient { ] localFunctionDefs[name] = functionDef + print("[MCPClient] 本地函数 \(name) 注册成功") + print("[MCPClient] 当前本地函数列表: \(Array(localFunctions.keys))") + return true } @@ -329,43 +478,69 @@ public class MCPClient { } for client in subClients.values { - allToolMaps.append(contentsOf: client.getToolMaps()) + let tools = client.getToolMaps() + print("[MCPClient] 读取工具列表: \(tools)") + allToolMaps.append(contentsOf: tools) } return allToolMaps } public func getToolType(name: String) -> ToolType? { + print("[MCPClient] 检查工具类型: \(name)") + print("[MCPClient] 本地函数列表: \(Array(localFunctions.keys))") + if localFunctions.keys.contains(name) { + print("[MCPClient] \(name) 是本地函数") return .localFunction } if subClients.values.contains(where: { $0.containsTool(name: name) }) { + print("[MCPClient] \(name) 是 MCP 工具") return .mcpTool } + print("[MCPClient] \(name) 未找到") return nil } public func callTool(name: String, arguments: [String: Any]) async -> [String: Any]? { + print("[MCPClient] 开始调用工具: \(name)") + print("[MCPClient] 参数: \(arguments)") + print("[MCPClient] 本地函数列表: \(Array(localFunctions.keys))") + print("[MCPClient] 子客户端数量: \(subClients.count)") + + // 检查本地函数 if let handler = localFunctions[name] { + print("[MCPClient] 找到本地函数: \(name),开始执行") do { let result = try await handler.handle(arguments: arguments) + print("[MCPClient] 本地函数 \(name) 执行成功") return ["context": result] } catch { + print("[MCPClient] 本地函数 \(name) 执行失败: \(error.localizedDescription)") return [ "content": [["type": "text", "text": "Function call failed: \(error.localizedDescription)"]], "isError": true ] } + } else { + print("[MCPClient] 未找到本地函数: \(name)") } - for client in subClients.values { + // 检查MCP工具 + print("[MCPClient] 开始检查 MCP 工具") + for (serverId, client) in subClients { + print("[MCPClient] 检查服务器 \(serverId) 是否包含工具 \(name)") if client.containsTool(name: name) { - return await client.callTool(name: name, arguments: arguments) + print("[MCPClient] 在服务器 \(serverId) 中找到工具 \(name),开始远程调用") + let result = await client.callTool(name: name, arguments: arguments) + print("[MCPClient] 远程工具 \(name) 调用完成") + return result } } + print("[MCPClient] 工具 \(name) 未找到") return [ "content": [["type": "text", "text": "Tool not found: \(name)"]], "isError": true @@ -386,7 +561,7 @@ public class MCPClient { } public func isConnected() -> Bool { - return !subClients.isEmpty + return !subClients.isEmpty && subClients.values.allSatisfy { $0.getConnectionStatus() } } public func disconnectAll() async { @@ -395,4 +570,40 @@ public class MCPClient { } subClients.removeAll() } -} \ No newline at end of file +} + +// 扩展NSLock以支持async/await +extension NSLock { + func withLock(_ body: () async throws -> T) async rethrows -> T { + lock() + defer { unlock() } + return try await body() + } +} + +// 添加超时支持 +func withTimeout(seconds: TimeInterval, operation: @escaping () async throws -> T) async throws -> T { + return try await withThrowingTaskGroup(of: T.self) { group in + group.addTask { + try await operation() + } + + group.addTask { + try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) + throw TimeoutError() + } + + guard let result = try await group.next() else { + throw TimeoutError() + } + + group.cancelAll() + return result + } +} + +struct TimeoutError: Swift.Error { + var localizedDescription: String { + return "操作超时" + } +} diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index 41c3df391..6a553d692 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -312,58 +312,186 @@ public class SystemFunctionHandler: NSObject { // MARK: - 功能处理器实现 /// 获取当前位置处理器 +// 移除这个重复的函数定义(第454-470行) +// private func withTimeout(seconds: TimeInterval, operation: @escaping () async throws -> T) async throws -> T { +// ... +// } + +// 在 GetCurrentLocationHandler 中修改超时处理 private class GetCurrentLocationHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { - // 检查位置权限 - guard LocationService.shared.hasLocationPermission() else { - let errorResult: [String: Any] = [ - "success": false, - "message": "位置权限被拒绝。请前往 设置 > 隐私与安全 > 定位服务 > Deep Voice 启用定位权限" - ] - let jsonData = try JSONSerialization.data(withJSONObject: errorResult) - return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}" - } + print("[GetCurrentLocationHandler] 开始处理位置请求") - // 先尝试获取缓存的位置数据 - if let cachedLocation = LocationService.shared.getCachedLocationData() { - let message = "当前位置:\(cachedLocation.address ?? "未知地址")(纬度 \(cachedLocation.latitude),经度 \(cachedLocation.longitude))" + return await withCheckedContinuation { continuation in + // 设置超时 + let timeoutTask = Task { + try await Task.sleep(nanoseconds: 15_000_000_000) // 15秒 + continuation.resume(returning: self.createErrorResponse(message: "位置获取超时,请检查网络连接后重试")) + } - // 异步触发最新定位(不等待结果) - Task { - do { - _ = try await LocationService.shared.getCurrentLocation() - } catch { - print("后台更新位置失败: \(error)") + LocationService.shared.getCurrentLocation { result in + timeoutTask.cancel() + + switch result { + case .success(let locationInfo): + print("[GetCurrentLocationHandler] 位置获取成功") + + // 解析位置数据 + guard let latStr = locationInfo["user_lat"] as? String, + let lngStr = locationInfo["user_lng"] as? String, + let latitude = Double(latStr), + let longitude = Double(lngStr), + let address = locationInfo["user_location"] as? String else { + print("[GetCurrentLocationHandler] 位置数据格式错误") + continuation.resume(returning: self.createErrorResponse(message: "位置数据格式错误,请重试")) + return + } + + print("[GetCurrentLocationHandler] 位置获取成功: lat=\(latitude), lng=\(longitude)") + + let message = "当前位置:\(address)(纬度 \(latitude),经度 \(longitude))" + let successResponse = self.createSuccessResponse(message: message, latitude: latitude, longitude: longitude, address: address) + + print("[GetCurrentLocationHandler] 返回位置结果") + continuation.resume(returning: successResponse) + + case .failure(let error): + print("[GetCurrentLocationHandler] 位置获取失败: \(error.localizedDescription)") + let errorMessage = self.getUserFriendlyErrorMessage(error) + continuation.resume(returning: self.createErrorResponse(message: errorMessage)) } } - - let result: [String: Any] = [ - "success": true, - "message": message - ] - let jsonData = try JSONSerialization.data(withJSONObject: result) - return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}" } + } + + private func createSuccessResponse(message: String, latitude: Double, longitude: Double, address: String) -> String { + let result: [String: Any] = [ + "success": true, + "message": message, + "data": [ + "latitude": latitude, + "longitude": longitude, + "address": address, + "timestamp": Date().timeIntervalSince1970 * 1000 + ] + ] - // 如果没有缓存数据,同步获取位置 do { - let locationData = try await LocationService.shared.getCurrentLocation() - let message = "当前位置:\(locationData.address ?? "未知地址")(纬度 \(locationData.latitude),经度 \(locationData.longitude))" - - let result: [String: Any] = [ - "success": true, - "message": message - ] let jsonData = try JSONSerialization.data(withJSONObject: result) return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}" } catch { - let errorResult: [String: Any] = [ - "success": false, - "message": "获取位置失败:\(error.localizedDescription)" - ] + return "{\"error\": \"JSON序列化失败\"}" + } + } + + private func createErrorResponse(message: String) -> String { + let errorResult: [String: Any] = [ + "success": false, + "message": message + ] + + do { let jsonData = try JSONSerialization.data(withJSONObject: errorResult) return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}" + } catch { + return "{\"error\": \"JSON序列化失败\"}" + } + } + + private func getUserFriendlyErrorMessage(_ error: Error) -> String { + let errorDescription = error.localizedDescription.lowercased() + + if errorDescription.contains("permission") || errorDescription.contains("权限") { + return "需要位置权限才能获取当前位置,请在设置中开启位置权限" + } else if errorDescription.contains("network") || errorDescription.contains("网络") { + return "网络连接异常,请检查网络后重试" + } else if errorDescription.contains("service") || errorDescription.contains("服务") { + return "位置服务暂时不可用,请稍后重试" + } else { + return "获取位置失败:\(error.localizedDescription)" + } + } +} + +// MARK: - 支持结构体 + +private struct LocationResult: Codable { + let success: Bool + let message: String + let data: LocationResultData? + let error: LocationErrorInfo? + let executionTime: TimeInterval + let timestamp: Double + + init(success: Bool, message: String, data: LocationResultData? = nil, error: LocationErrorInfo? = nil, executionTime: TimeInterval) { + self.success = success + self.message = message + self.data = data + self.error = error + self.executionTime = executionTime + self.timestamp = Date().timeIntervalSince1970 * 1000 + } + + func toJSONString() throws -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let jsonData = try encoder.encode(self) + return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}" + } +} + +private struct LocationResultData: Codable { + let latitude: Double + let longitude: Double + let address: String? + let timestamp: Double + let executionTime: TimeInterval +} + +private struct LocationErrorInfo: Codable { + let code: String + let type: String + let description: String +} + + +// MARK: - 自定义错误类型 + +private enum LocationError: Error { + case timeout + case permissionDenied + case serviceUnavailable + case invalidData + case networkError + + var code: String { + switch self { + case .timeout: return "TIMEOUT" + case .permissionDenied: return "PERMISSION_DENIED" + case .serviceUnavailable: return "SERVICE_UNAVAILABLE" + case .invalidData: return "INVALID_DATA" + case .networkError: return "NETWORK_ERROR" + } + } + + var type: String { + switch self { + case .timeout: return "timeout" + case .permissionDenied: return "permission" + case .serviceUnavailable: return "service" + case .invalidData: return "data" + case .networkError: return "network" + } + } + + var userFriendlyMessage: String { + switch self { + case .timeout: return "位置获取超时,请检查网络连接后重试" + case .permissionDenied: return "需要位置权限才能获取当前位置,请在设置中开启位置权限" + case .serviceUnavailable: return "位置服务暂时不可用,请稍后重试" + case .invalidData: return "位置数据格式错误,请重试" + case .networkError: return "网络连接异常,请检查网络后重试" } } } @@ -587,3 +715,8 @@ private class OpenRecorderHandler: FunctionHandler { return "{\"result\": \"无法打开录音机应用\"}" } } + +// 同时移除 LocationError 枚举定义(第472-510行),因为它没有被使用 +// private enum LocationError: Error { +// ... +// } diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift index 04c991abc..2312b1fc4 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift @@ -9,349 +9,368 @@ public extension Notification.Name { public class LocationService: NSObject { private static let TAG = "LocationService" - private static let CACHE_PREFS_NAME = "location_cache" - private static let PERMISSION_REQUEST_CODE = 1001 public static let shared = LocationService() private var locationManager: CLLocationManager? - private var initialized = false - private var permissionRetryTimer: Timer? - - // 存储当前的定位请求回调(Flutter用) + private var cachedLocationData: LocationData? private var currentLocationCallback: ((Result<[String: Any], Error>) -> Void)? - // 存储权限请求回调(Flutter用) - private var permissionRequestCallback: ((Result<[String: Bool], Error>) -> Void)? - // 存储服务间调用的回调 - private var serviceCallbacks: [LocationCallback] = [] + // 缓存有效期(5分钟) + private let cacheValidityDuration: TimeInterval = 5 * 60 - // 缓存的位置数据 - private var cachedLocationData: LocationData? + // 权限状态缓存 + private var authorizationStatus: CLAuthorizationStatus = .notDetermined + private var isLocationServicesEnabled: Bool = false private override init() { super.init() - } - - /// 检查应用是否在前台运行 - public func isAppInForeground() -> Bool { - return UIApplication.shared.applicationState == .active - } - - /// 初始化定位服务 - public func initialize() { - if initialized { return } - - if !checkPermissions() { - print("\(LocationService.TAG): 没有定位权限,等待用户授权中...") - startPermissionMonitor() - return - } - setupLocationManager() - initialized = true - print("\(LocationService.TAG): 定位服务初始化完成") } private func setupLocationManager() { - locationManager = CLLocationManager() - locationManager?.delegate = self - locationManager?.desiredAccuracy = kCLLocationAccuracyBest - locationManager?.distanceFilter = 10 - } - - private func startPermissionMonitor() { - permissionRetryTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in - guard let self = self else { return } - let hasPermission = self.checkPermissions() - if hasPermission && !self.initialized { - print("\(LocationService.TAG): 权限已授予,重新初始化定位服务") - self.initialize() - self.stopPermissionMonitor() - } + DispatchQueue.main.async { [weak self] in + self?.locationManager = CLLocationManager() + self?.locationManager?.delegate = self + self?.locationManager?.desiredAccuracy = kCLLocationAccuracyBest + self?.locationManager?.distanceFilter = 10 + + // 初始化时获取当前状态 + self?.authorizationStatus = CLLocationManager.authorizationStatus() + self?.isLocationServicesEnabled = CLLocationManager.locationServicesEnabled() + + print("[LocationService] LocationManager 初始化完成") + print("[LocationService] 初始权限状态: \(self?.authorizationStatus.rawValue ?? -1)") + print("[LocationService] 位置服务启用: \(self?.isLocationServicesEnabled ?? false)") } } - private func stopPermissionMonitor() { - permissionRetryTimer?.invalidate() - permissionRetryTimer = nil - } - - /// 检查定位权限 - private func checkPermissions() -> Bool { - return hasForegroundLocationPermission() - } - - private func hasForegroundLocationPermission() -> Bool { - let status = CLLocationManager.authorizationStatus() - return status == .authorizedWhenInUse || status == .authorizedAlways - } - - private func hasBackgroundLocationPermission() -> Bool { - return CLLocationManager.authorizationStatus() == .authorizedAlways - } - - /// 检查定位权限状态(Flutter用) - public func checkLocationPermission(completion: @escaping (Result<[String: Bool], Error>) -> Void) { - let hasPermission = checkPermissions() - let result: [String: Bool] = [ - "hasPermission": hasPermission, - "hasForegroundPermission": hasForegroundLocationPermission(), - "hasBackgroundPermission": hasBackgroundLocationPermission() - ] - completion(.success(result)) - } - - /// 请求定位权限(Flutter用) - public func requestLocationPermission(completion: @escaping (Result<[String: Bool], Error>) -> Void) { - if checkPermissions() { - completion(.success(["granted": true])) + /// 获取当前位置 - 主要接口 + public func getCurrentLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) { + print("[LocationService] 开始获取当前位置") + + // 1. 检查缓存数据是否存在且有效 + if let cachedData = getCachedLocationIfValid() { + print("[LocationService] 返回缓存位置数据") + let locationInfo = formatLocationData(cachedData) + completion(.success(locationInfo)) + + // 异步更新缓存 + print("[LocationService] 异步更新位置缓存") + asyncUpdateLocation() return } - permissionRequestCallback = completion - locationManager?.requestWhenInUseAuthorization() + // 2. 缓存不存在或已过期,检查权限并同步获取位置 + print("[LocationService] 缓存无效,开始同步获取位置") + syncGetLocation(completion: completion) } - /// 按需获取当前位置(Flutter用) - public func getCurrentLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) { - if !checkPermissions() { - completion(.failure(NSError(domain: "LocationService", code: -2, userInfo: [NSLocalizedDescriptionKey: "需要定位权限"]))) - return + /// 检查缓存是否有效 + private func getCachedLocationIfValid() -> LocationData? { + guard let cached = cachedLocationData else { + print("[LocationService] 无缓存数据") + return nil } - if !initialized { - completion(.failure(NSError(domain: "LocationService", code: -3, userInfo: [NSLocalizedDescriptionKey: "定位服务未初始化"]))) - return - } + let currentTime = Date().timeIntervalSince1970 * 1000 + let cacheAge = currentTime - cached.timestamp - if currentLocationCallback != nil { - completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "正在定位中,请稍后再试"]))) - return + if cacheAge < cacheValidityDuration * 1000 { + print("[LocationService] 缓存有效,年龄: \(cacheAge/1000)秒") + return cached + } else { + print("[LocationService] 缓存已过期,年龄: \(cacheAge/1000)秒") + return nil } - - // 保存回调,开始定位 - currentLocationCallback = completion - locationManager?.requestLocation() } - // MARK: - 供其他iOS服务使用的接口 - - /// 检查是否有定位权限(供其他服务使用) - public func hasLocationPermission() -> Bool { - return checkPermissions() + /// 异步更新位置缓存 + private func asyncUpdateLocation() { + // 检查权限 + guard checkLocationPermission() else { + print("[LocationService] 异步更新失败:无位置权限") + return + } + + // 异步请求位置更新 + DispatchQueue.global(qos: .background).async { [weak self] in + self?.requestLocationUpdate { result in + switch result { + case .success(let locationData): + print("[LocationService] 异步更新位置缓存成功") + self?.cachedLocationData = locationData + case .failure(let error): + print("[LocationService] 异步更新位置缓存失败: \(error.localizedDescription)") + } + } + } } - /// 异步获取当前位置(供其他iOS服务使用) - public func getCurrentLocation() async throws -> LocationData { - return try await withCheckedThrowingContinuation { continuation in - getCurrentLocation { result in + /// 同步获取位置 + private func syncGetLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) { + // 检查权限 + guard checkLocationPermission() else { + print("[LocationService] 无位置权限,尝试请求权限") + requestLocationPermission { [weak self] result in switch result { - case .success(let locationInfo): - // 从返回的字典中提取位置信息 - if let latStr = locationInfo["user_lat"] as? String, - let lngStr = locationInfo["user_lng"] as? String, - let latitude = Double(latStr), - let longitude = Double(lngStr), - let address = locationInfo["user_location"] as? String { - - let locationData = LocationData( - latitude: latitude, - longitude: longitude, - address: address, - timestamp: Date().timeIntervalSince1970 * 1000 - ) - continuation.resume(returning: locationData) + case .success(let granted): + if granted { + print("[LocationService] 权限获取成功,重新获取位置") + self?.syncGetLocation(completion: completion) } else { - continuation.resume(throwing: NSError(domain: "LocationService", code: -5, userInfo: [NSLocalizedDescriptionKey: "位置数据格式错误"])) + completion(.failure(NSError(domain: "LocationService", code: -1, userInfo: [NSLocalizedDescriptionKey: "位置权限被拒绝"]))) } case .failure(let error): - continuation.resume(throwing: error) + completion(.failure(error)) } } - } - } - - /// 获取当前位置(供其他iOS服务使用) - public func requestLocation(callback: LocationCallback) { - if !checkPermissions() { - callback.onLocationError(errorCode: -2, errorMessage: "没有定位权限") - return - } - - if !initialized { - callback.onLocationError(errorCode: -3, errorMessage: "定位服务未初始化") return } - // 添加到回调列表 - serviceCallbacks.append(callback) - - // 如果没有正在进行的定位请求,开始定位 - if currentLocationCallback == nil && serviceCallbacks.count == 1 { - locationManager?.requestLocation() + // 有权限,直接获取位置 + requestLocationUpdate { [weak self] result in + switch result { + case .success(let locationData): + print("[LocationService] 同步获取位置成功") + self?.cachedLocationData = locationData + let locationInfo = self?.formatLocationData(locationData) ?? [:] + completion(.success(locationInfo)) + case .failure(let error): + print("[LocationService] 同步获取位置失败: \(error.localizedDescription)") + completion(.failure(error)) + } } } - /// 同步获取缓存的位置信息(供其他服务使用) - /// 注意:这个方法返回的可能是过期的位置信息 - public func getCachedLocationData() -> LocationData? { - // 优先返回内存中的缓存 - if let cached = cachedLocationData { - return cached - } - - // 从UserDefaults中读取缓存 - return loadCachedLocationFromUserDefaults() + /// 检查位置权限 - 使用缓存的状态 + private func checkLocationPermission() -> Bool { + return authorizationStatus == .authorizedWhenInUse || authorizationStatus == .authorizedAlways } - private func saveCachedLocationToUserDefaults(_ locationData: LocationData) { - do { - let encoder = JSONEncoder() - let data = try encoder.encode(locationData) - UserDefaults.standard.set(data, forKey: "latitude") - UserDefaults.standard.set(String(locationData.latitude), forKey: "latitude") - UserDefaults.standard.set(String(locationData.longitude), forKey: "longitude") - UserDefaults.standard.set(locationData.address, forKey: "address") - UserDefaults.standard.set(Double(locationData.timestamp), forKey: "timestamp") - UserDefaults.standard.synchronize() - } catch { - print("\(LocationService.TAG): 保存位置缓存失败: \(error.localizedDescription)") + /// 请求位置权限 - 异步处理 + private func requestLocationPermission(completion: @escaping (Result) -> Void) { + print("[LocationService] 当前缓存的权限状态: \(authorizationStatus.rawValue)") + + switch authorizationStatus { + case .notDetermined: + print("[LocationService] 请求位置权限") + // 保存回调,等待权限结果 + permissionCallback = completion + DispatchQueue.main.async { [weak self] in + self?.locationManager?.requestWhenInUseAuthorization() + } + case .denied, .restricted: + print("[LocationService] 位置权限被拒绝或受限") + completion(.success(false)) + case .authorizedWhenInUse, .authorizedAlways: + print("[LocationService] 已有位置权限") + completion(.success(true)) + @unknown default: + completion(.failure(NSError(domain: "LocationService", code: -2, userInfo: [NSLocalizedDescriptionKey: "未知权限状态"]))) } } - private func loadCachedLocationFromUserDefaults() -> LocationData? { - do { - guard let latitudeStr = UserDefaults.standard.string(forKey: "latitude"), - let longitudeStr = UserDefaults.standard.string(forKey: "longitude"), - let latitude = Double(latitudeStr), - let longitude = Double(longitudeStr) else { - return nil + /// 请求位置更新 - 优化版本 + private func requestLocationUpdate(completion: @escaping (Result) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + guard let locationManager = self.locationManager else { + print("[LocationService] 错误:LocationManager 未初始化") + completion(.failure(NSError(domain: "LocationService", code: -3, userInfo: [NSLocalizedDescriptionKey: "LocationManager 未初始化"]))) + return } - let address = UserDefaults.standard.string(forKey: "address") ?? "未知地址" - let timestampDouble = UserDefaults.standard.double(forKey: "timestamp") + // 检查是否已有请求在进行 + if self.currentLocationCallback != nil { + print("[LocationService] 错误:位置请求正在进行中,拒绝新请求") + completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "位置请求正在进行中"]))) + return + } - if timestampDouble == 0 { return nil } + // 使用缓存的状态信息 + print("[LocationService] 开始位置请求") + print("[LocationService] 缓存的权限状态: \(self.authorizationStatus.rawValue)") + print("[LocationService] 缓存的位置服务状态: \(self.isLocationServicesEnabled)") + print("[LocationService] 当前线程: \(Thread.current)") + print("[LocationService] 是否主线程: \(Thread.isMainThread)") - return LocationData( - latitude: latitude, - longitude: longitude, - address: address, - timestamp: TimeInterval(timestampDouble) - ) - } catch { - print("\(LocationService.TAG): 读取位置缓存失败: \(error.localizedDescription)") - return nil - } - } - - /// 停止定位服务 - public func stop() { - locationManager?.stopUpdatingLocation() - locationManager = nil - currentLocationCallback = nil - permissionRequestCallback = nil - serviceCallbacks.removeAll() - initialized = false - stopPermissionMonitor() - } - - private func handleLocationResult(_ location: CLLocation?, error: Error?) { - // 处理Flutter回调 - let flutterCallback = currentLocationCallback - currentLocationCallback = nil - - if let location = location, error == nil { - print("\(LocationService.TAG): 定位成功: \(location.coordinate.latitude), \(location.coordinate.longitude)") + // 验证权限状态 + if !self.checkLocationPermission() { + print("[LocationService] 错误:无位置权限") + completion(.failure(NSError(domain: "LocationService", code: -5, userInfo: [NSLocalizedDescriptionKey: "无位置权限"]))) + return + } - // 进行逆地理编码获取地址 - let geocoder = CLGeocoder() - geocoder.reverseGeocodeLocation(location) { [weak self] placemarks, geocodeError in - guard let self = self else { return } - - let address = placemarks?.first?.name ?? "未知地址" - - // 缓存位置数据 - let locationData = LocationData( - latitude: location.coordinate.latitude, - longitude: location.coordinate.longitude, - address: address, - timestamp: TimeInterval(Date().timeIntervalSince1970) * 1000 - ) - self.cachedLocationData = locationData - self.saveCachedLocationToUserDefaults(locationData) - - // 通知Flutter - if let callback = flutterCallback { - let result: [String: Any] = [ - "user_location": address, - "user_lat": String(location.coordinate.latitude), - "user_lng": String(location.coordinate.longitude) - ] - callback(.success(result)) - } - - // 通知其他iOS服务 - self.serviceCallbacks.forEach { callback in - callback.onLocationSuccess( - latitude: location.coordinate.latitude, - longitude: location.coordinate.longitude, - address: address - ) - } - - // 清空服务回调列表(单次定位) - self.serviceCallbacks.removeAll() + if !self.isLocationServicesEnabled { + print("[LocationService] 错误:位置服务未启用") + completion(.failure(NSError(domain: "LocationService", code: -6, userInfo: [NSLocalizedDescriptionKey: "位置服务未启用"]))) + return } - } else { - let errorMessage = error?.localizedDescription ?? "未知错误" - print("\(LocationService.TAG): 定位失败: \(errorMessage)") - // 通知Flutter - if let callback = flutterCallback { - callback(.failure(NSError(domain: "LocationService", code: -1, userInfo: [NSLocalizedDescriptionKey: "定位失败: \(errorMessage)"]))) + // 强制重新设置 delegate + if locationManager.delegate == nil { + print("[LocationService] 警告:Delegate 为空,重新设置") + locationManager.delegate = self } - // 通知其他iOS服务 - serviceCallbacks.forEach { callback in - callback.onLocationError(errorCode: -1, errorMessage: errorMessage) + // 验证 delegate + if locationManager.delegate === self { + print("[LocationService] Delegate 验证成功") + } else { + print("[LocationService] 错误:Delegate 设置失败") + completion(.failure(NSError(domain: "LocationService", code: -8, userInfo: [NSLocalizedDescriptionKey: "Delegate 设置失败"]))) + return } - // 清空服务回调列表 - serviceCallbacks.removeAll() + // 设置回调 + self.currentLocationCallback = { result in + switch result { + case .success(let locationInfo): + print("[LocationService] 位置回调成功,开始解析数据") + if let latStr = locationInfo["user_lat"] as? String, + let lngStr = locationInfo["user_lng"] as? String, + let latitude = Double(latStr), + let longitude = Double(lngStr) { + + print("[LocationService] 位置数据解析成功: lat=\(latitude), lng=\(longitude)") + let locationData = LocationData( + latitude: latitude, + longitude: longitude, + address: "", + timestamp: Date().timeIntervalSince1970 * 1000 + ) + completion(.success(locationData)) + } else { + print("[LocationService] 错误:位置数据格式错误,原始数据: \(locationInfo)") + completion(.failure(NSError(domain: "LocationService", code: -7, userInfo: [NSLocalizedDescriptionKey: "位置数据格式错误"]))) + } + case .failure(let error): + print("[LocationService] 位置回调失败: \(error.localizedDescription)") + completion(.failure(error)) + } + } + + print("[LocationService] 调用 locationManager.requestLocation()") + locationManager.requestLocation() + print("[LocationService] requestLocation() 调用完成,等待回调") + + // 30秒超时机制 + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { + if self.currentLocationCallback != nil { + print("[LocationService] 位置请求超时") + let callback = self.currentLocationCallback + self.currentLocationCallback = nil + callback?(.failure(NSError(domain: "LocationService", code: -9, userInfo: [NSLocalizedDescriptionKey: "位置请求超时"]))) + } + } } } + + /// 格式化位置数据 + private func formatLocationData(_ locationData: LocationData) -> [String: Any] { + return [ + "user_lat": String(locationData.latitude), + "user_lng": String(locationData.longitude), + "user_location": locationData.address, + "timestamp": locationData.timestamp + ] + } + + // MARK: - 权限回调 + private var permissionCallback: ((Result) -> Void)? } // MARK: - CLLocationManagerDelegate extension LocationService: CLLocationManagerDelegate { + // 替换 didUpdateLocations 方法中的逆地理编码部分 public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let location = locations.last else { return } - handleLocationResult(location, error: nil) + print("[LocationService] didUpdateLocations 被调用,位置数量: \(locations.count)") + + guard let location = locations.last else { + print("[LocationService] 错误:locations 数组为空") + return + } + + print("[LocationService] 位置更新成功: lat=\(location.coordinate.latitude), lng=\(location.coordinate.longitude)") + print("[LocationService] 位置精度: \(location.horizontalAccuracy)m") + + // 直接使用经纬度,不进行逆地理编码 + let locationInfo: [String: Any] = [ + "user_lat": String(location.coordinate.latitude), + "user_lng": String(location.coordinate.longitude), + "timestamp": Date().timeIntervalSince1970 * 1000 + ] + + print("[LocationService] 准备调用位置回调") + // 调用回调 + let callback = currentLocationCallback + currentLocationCallback = nil + callback?(.success(locationInfo)) + print("[LocationService] 位置回调已调用") } public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - handleLocationResult(nil, error: error) + print("[LocationService] didFailWithError 被调用") + print("[LocationService] 位置获取失败: \(error.localizedDescription)") + print("[LocationService] 错误代码: \((error as NSError).code)") + print("[LocationService] 错误域: \((error as NSError).domain)") + + let callback = currentLocationCallback + currentLocationCallback = nil + print("[LocationService] 准备调用错误回调") + callback?(.failure(error)) + print("[LocationService] 错误回调已调用") } public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - print("\(LocationService.TAG): 定位权限状态变更: \(status.rawValue)") + print("[LocationService] didChangeAuthorization 被调用") + print("[LocationService] 权限状态变更: \(status.rawValue) (\(authorizationStatusString(status)))") + + // 更新缓存的状态 + authorizationStatus = status + isLocationServicesEnabled = CLLocationManager.locationServicesEnabled() + + print("[LocationService] 更新缓存状态 - 权限: \(authorizationStatus.rawValue), 服务启用: \(isLocationServicesEnabled)") + + guard let callback = permissionCallback else { + print("[LocationService] 无权限回调,忽略权限变更") + return + } switch status { case .authorizedWhenInUse, .authorizedAlways: - if !initialized { - initialize() - } - permissionRequestCallback?(.success(["granted": true])) - permissionRequestCallback = nil + print("[LocationService] 权限获取成功") + permissionCallback = nil + callback(.success(true)) case .denied, .restricted: - permissionRequestCallback?(.success(["granted": false])) - permissionRequestCallback = nil + print("[LocationService] 权限被拒绝或受限") + permissionCallback = nil + callback(.success(false)) case .notDetermined: - break + print("[LocationService] 权限状态仍未确定,继续等待") + // 继续等待用户选择 + permissionCallback = callback @unknown default: - break + print("[LocationService] 未知权限状态") + permissionCallback = nil + callback(.failure(NSError(domain: "LocationService", code: -10, userInfo: [NSLocalizedDescriptionKey: "未知权限状态"]))) + } + } + + // 辅助方法:将权限状态转换为可读字符串 + private func authorizationStatusString(_ status: CLAuthorizationStatus) -> String { + switch status { + case .notDetermined: return "notDetermined" + case .restricted: return "restricted" + case .denied: return "denied" + case .authorizedAlways: return "authorizedAlways" + case .authorizedWhenInUse: return "authorizedWhenInUse" + @unknown default: return "unknown" } } } + +// Remove the duplicate LocationData definition from here +// The LocationData struct is already defined in LocationData.swift diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift index 80b383126..ba8222b63 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift @@ -13,16 +13,10 @@ public class LocationServicePlugin: NSObject, FlutterPlugin { registrar.addMethodCallDelegate(instance, channel: channel) self.channel = channel - instance.addStartLocationMonitor() } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { - case "location": - result(nil) - case "initialize": - LocationService.shared.initialize() - result(nil) case "getCurrentLocation": LocationService.shared.getCurrentLocation { locationResult in switch locationResult { @@ -32,41 +26,9 @@ public class LocationServicePlugin: NSObject, FlutterPlugin { result(FlutterError(code: "LOCATION_ERROR", message: error.localizedDescription, details: nil)) } } - case "checkPermission": - LocationService.shared.checkLocationPermission { permissionResult in - switch permissionResult { - case .success(let permissions): - result(permissions) - case .failure(let error): - result(FlutterError(code: "PERMISSION_ERROR", message: error.localizedDescription, details: nil)) - } - } - case "requestPermission": - LocationService.shared.requestLocationPermission { permissionResult in - switch permissionResult { - case .success(let permissions): - result(permissions) - case .failure(let error): - result(FlutterError(code: "PERMISSION_ERROR", message: error.localizedDescription, details: nil)) - } - } - case "stop": - LocationService.shared.stop() - result(nil) default: result(FlutterMethodNotImplemented) } } - - private func addStartLocationMonitor() { - NotificationCenter.default.addObserver( - forName: .locationStartEvent, - object: nil, - queue: .main - ) { notification in - print("📥 收到启动定位服务事件") - LocationService.shared.initialize() - } - } } -#endif \ No newline at end of file +#endif diff --git a/local_plugins/music_service/ios/music_service.podspec b/local_plugins/music_service/ios/music_service.podspec index d9aa1dcb3..61019dd4b 100644 --- a/local_plugins/music_service/ios/music_service.podspec +++ b/local_plugins/music_service/ios/music_service.podspec @@ -15,7 +15,7 @@ A new Flutter project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '12.0' + s.platform = :ios, '16.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/local_plugins/ota/ios/ota.podspec b/local_plugins/ota/ios/ota.podspec index 39c28fd0a..b4e84fa29 100644 --- a/local_plugins/ota/ios/ota.podspec +++ b/local_plugins/ota/ios/ota.podspec @@ -15,7 +15,7 @@ A new Flutter project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '12.0' + s.platform = :ios, '16.0' s.vendored_frameworks = [ 'Frameworks/DFUnits.framework', 'Frameworks/JL_AdvParse.framework', diff --git a/local_plugins/spotify_sdk/ios/spotify_sdk.podspec b/local_plugins/spotify_sdk/ios/spotify_sdk.podspec index 7be779eb9..dbb91fa4b 100644 --- a/local_plugins/spotify_sdk/ios/spotify_sdk.podspec +++ b/local_plugins/spotify_sdk/ios/spotify_sdk.podspec @@ -15,8 +15,8 @@ Unofficial Spotify Flutter SDK. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '12.0' - s.ios.deployment_target = '12.0' + s.platform = :ios, '16.0' + s.ios.deployment_target = '16.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } s.swift_version = '5.0' diff --git a/local_plugins/user_portrait/ios/user_portrait/Package.swift b/local_plugins/user_portrait/ios/user_portrait/Package.swift index 4d8e0e3fc..3519e97aa 100644 --- a/local_plugins/user_portrait/ios/user_portrait/Package.swift +++ b/local_plugins/user_portrait/ios/user_portrait/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "user_portrait", platforms: [ - .iOS("12.0") + .iOS("16.0") ], products: [ .library(name: "user-portrait", targets: ["user_portrait"]) diff --git a/local_plugins/volcano_speech/ios/volcano_speech.podspec b/local_plugins/volcano_speech/ios/volcano_speech.podspec index 9a70baed1..42b24bd77 100644 --- a/local_plugins/volcano_speech/ios/volcano_speech.podspec +++ b/local_plugins/volcano_speech/ios/volcano_speech.podspec @@ -15,7 +15,7 @@ A Flutter plugin for Volcano Speech services. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '12.0' + s.platform = :ios, '16.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }