Browse Source

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

weicu
lxm 1 year ago
parent
commit
7a6c121318
  1. 201
      ios/Podfile.lock
  2. 11
      ios/Runner.xcodeproj/project.pbxproj
  3. 4
      lib/data/services/network/api.dart
  4. 2
      lib/modules/devices/controllers/devices_controller.dart
  5. 21
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  6. 2
      local_plugins/amap_service/ios/Classes/AmapService.swift
  7. 4
      local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift
  8. 2
      local_plugins/amap_service/ios/amap_service.podspec
  9. 2
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  10. 8
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleConst.swift
  11. 79
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
  12. 44
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift
  13. 98
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/CustomSseClientTransport.swift
  14. 235
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift
  15. 207
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  16. 551
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift
  17. 40
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift
  18. 2
      local_plugins/music_service/ios/music_service.podspec
  19. 2
      local_plugins/ota/ios/ota.podspec
  20. 4
      local_plugins/spotify_sdk/ios/spotify_sdk.podspec
  21. 2
      local_plugins/user_portrait/ios/user_portrait/Package.swift
  22. 2
      local_plugins/volcano_speech/ios/volcano_speech.podspec

201
ios/Podfile.lock

@ -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

11
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;
};

4
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,
);
}

2
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) {

21
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)")
}

2
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) {

4
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)
}
}
}
}

2
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

2
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

8
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
}
}

79
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)
}
// 更新缓存(无论是否上报都要更新)

44
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)"
}
}
}

98
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<Void, Never>?
// 消息流
private let messageStream: AsyncThrowingStream<Data, Swift.Error>
@ -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)
}
}
// 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
}
}

235
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<Void, Never>?
// 连接状态锁
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()
}
}
}
// 扩展NSLock以支持async/await
extension NSLock {
func withLock<T>(_ body: () async throws -> T) async rethrows -> T {
lock()
defer { unlock() }
return try await body()
}
}
// 添加超时支持
func withTimeout<T>(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 "操作超时"
}
}

207
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<T>(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 {
// ...
// }

551
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<Bool, Error>) -> 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<LocationData, Error>) -> 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<Bool, Error>) -> 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

40
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
#endif

2
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' }

2
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',

4
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'

2
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"])

2
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' }

Loading…
Cancel
Save