From ed9887a3cadf6a5835c9ffb3b1062009872c0001 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 16 Jun 2025 11:38:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0iOS=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 11 ++ .../agent_service/AgentServiceImpl.swift | 37 ++++- .../chat_api/SystemFunctionHandler.swift | 2 +- .../ios/Classes/LocationService.swift | 131 ++++++++++++++++++ .../ios/Classes/LocationServicePlugin.swift | 44 ++++++ .../ios/location_service.podspec | 18 ++- .../ios/location_service/Package.swift | 32 ----- .../LocationServicePlugin.swift | 19 --- .../location_service/PrivacyInfo.xcprivacy | 14 -- .../lib/location_service.dart | 27 +--- local_plugins/location_service/pubspec.yaml | 43 ------ 11 files changed, 230 insertions(+), 148 deletions(-) create mode 100644 local_plugins/location_service/ios/Classes/LocationService.swift create mode 100644 local_plugins/location_service/ios/Classes/LocationServicePlugin.swift delete mode 100644 local_plugins/location_service/ios/location_service/Package.swift delete mode 100644 local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift delete mode 100644 local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dda0379b0..4ef33cfa7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,6 +3,8 @@ PODS: - AMapNavi (~> 10.0.900) - Flutter - AMapFoundation (1.8.2) + - AMapLocation (2.10.0): + - AMapFoundation (>= 1.8.0) - AMapNavi (10.0.900): - AMapFoundation (>= 1.8.2) - audio_waveforms (0.0.1): @@ -54,6 +56,9 @@ PODS: - libwebp/sharpyuv (1.5.0) - libwebp/webp (1.5.0): - libwebp/sharpyuv + - location_service (0.0.1): + - AMapLocation (~> 2.10.0) + - Flutter - Mantle (2.2.0): - Mantle/extobjc (= 2.2.0) - Mantle/extobjc (2.2.0) @@ -98,6 +103,7 @@ DEPENDENCIES: - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) - fluwx (from `.symlinks/plugins/fluwx/ios`) - haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`) + - location_service (from `.symlinks/plugins/location_service/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`) @@ -107,6 +113,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - AMapFoundation + - AMapLocation - AMapNavi - FBAEMKit - FBSDKCoreKit @@ -146,6 +153,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/fluwx/ios" haptic_feedback: :path: ".symlinks/plugins/haptic_feedback/ios" + location_service: + :path: ".symlinks/plugins/location_service/ios" ota: :path: ".symlinks/plugins/ota/ios" permission_handler_apple: @@ -160,6 +169,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec + AMapLocation: 5248aec2455ebb5d104b367813c946430a2ee033 AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d @@ -177,6 +187,7 @@ SPEC CHECKSUMS: fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 + location_service: fab4ef23d473296a215c49e12c129e466574d94e Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d ota: 20c3e522c57e7ba70ce118109e73650acc9fec76 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d 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 9f4ac434a..e5035bf17 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 @@ -12,7 +12,11 @@ import music_service protocol AgentServiceListener: AnyObject { func onEvent(eventName: String, data: [String: Any]) } - +extension Notification.Name { + static let navigationdEvent = Notification.Name("navigationdEvent") //导航事件 + static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位 + static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件 +} class AgentServiceImpl: NSObject { private let TAG = "AgentServiceImpl" @@ -55,6 +59,7 @@ class AgentServiceImpl: NSObject { private let maxIdleSeconds: TimeInterval = 10 internal var audioPlayer: AudioPlayer? + private var locationInfo:[String: String]? internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl") @@ -161,7 +166,9 @@ class AgentServiceImpl: NSObject { sendError("Azure语音区域缺失", code: "CONFIG_ERROR") return false } - + //启动定位服务 + NotificationCenter.default.post(name: .locationstartEvent, object: nil) + addlocationmonitor() if let mcpServer = config["mcpServer"] as? String { self.mcpServer = mcpServer } @@ -441,7 +448,10 @@ class AgentServiceImpl: NSObject { var messages: [[String: Any]] = [] if !systemPrompt.isEmpty { - messages.append(chatApiService.createSystemMessage(content: systemPrompt)) + + let info: [String: String] = self.locationInfo ?? ["user_location": "还未获取到位置信息", "user_lat": "未开启定位", "user_lng": "未开启定位"] + let _systemPrompt = fillTemplate(systemPrompt, with: info) + messages.append(chatApiService.createSystemMessage(content: _systemPrompt)) os_log("添加系统提示消息", log: logger, type: .info) } @@ -690,6 +700,27 @@ class AgentServiceImpl: NSObject { MusicService.shared.playPlaylist(songs: songs) } + // 添加定位监听 + private func addlocationmonitor(){ + // 通常放在启动后或模块初始化时 + NotificationCenter.default.addObserver( + forName: .updatelocationEvent, + object: nil, + queue: .main + ) { notification in + if let info = notification.userInfo as? [String: String] { + print("📤 地址信息:\(info)") + self.locationInfo = info + } + } + } + func fillTemplate(_ template: String, with values: [String: String]) -> String { + var result = template + for (key, value) in values { + result = result.replacingOccurrences(of: "{\(key)}", with: value) + } + return result + } func clearChatHistory() -> Bool { chatHistory.removeAll() 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 610ccdc00..1d7b7746d 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 @@ -67,7 +67,7 @@ public class SystemFunctionHandler: NSObject { registerGetCurrentTimeFunction(client: client) // 注册获取当前位置函数 - registerGetCurrentLocationFunction(client: client) + // registerGetCurrentLocationFunction(client: client) // 注册媒体控制函数 registerMediaPlayFunction(client: client) diff --git a/local_plugins/location_service/ios/Classes/LocationService.swift b/local_plugins/location_service/ios/Classes/LocationService.swift new file mode 100644 index 000000000..d71179ee3 --- /dev/null +++ b/local_plugins/location_service/ios/Classes/LocationService.swift @@ -0,0 +1,131 @@ +import Foundation +import AMapLocationKit + +extension Notification.Name { + static let locationstartEvent = Notification.Name("locationstartEvent") //启动定位 + static let updatelocationEvent = Notification.Name("updatelocationEvent") //更新位置事件 +} +class LocationService: NSObject { + static let shared = LocationService() + private let TAG = "LocationService" + + private var locationManager: AMapLocationManager! + private var currentLocation: CLLocation? + private var currentReGeocode: AMapLocationReGeocode? + private var initialized = false + + private override init() { + super.init() + } + + // 初始化定位服务 + func initialize() { + if initialized { return } + print("\(TAG)--------- 初始化高德定位服务") + + locationManager = AMapLocationManager() + locationManager.delegate = self + + locationManager.desiredAccuracy = kCLLocationAccuracyBest // 定位精度 + locationManager.distanceFilter = 10.0 // 移动距离阈值(单位:米) + locationManager.locatingWithReGeocode = true // 是否返回逆地理编码信息 + locationManager.allowsBackgroundLocationUpdates = true // 是否允许后台定位 + locationManager.locationTimeout = 10 // 定位超时时间(秒) + locationManager.reGeocodeTimeout = 5 // 逆地理编码超时时间(秒) + + locationManager.startUpdatingLocation() + + initialized = true + } + + // 获取缓存的位置信息 + func getCachedLocation() -> [String: String]? { + guard let location = currentLocation else { return nil } + + var locationInfo: [String: String] = [ + "user_lat": "\(location.coordinate.latitude)", + "user_lng": "\(location.coordinate.longitude)" + ] + + if let regeocode = currentReGeocode { + let address = [ + regeocode.province, // 省 + regeocode.city, // 市 + regeocode.district, // 区 + regeocode.street, // 街道 + regeocode.number // 门牌号 + ].compactMap { $0 }.joined(separator: "") + + locationInfo["user_location"] = address + } + + return locationInfo + } + + // 获取当前位置(带回调) + func getCurrentLocation(completion: @escaping ([String: String]?, Error?) -> Void) { + if let cached = getCachedLocation() { + completion(cached, nil) + } else { + let error = NSError( + domain: "LocationService", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "当前位置还未获取"] + ) + completion(nil, error) + } + } + + // 停止定位服务 + func stop() { + locationManager.stopUpdatingLocation() + locationManager = nil + currentLocation = nil + currentReGeocode = nil + initialized = false + } +} + +// MARK: - AMapLocationManagerDelegate +extension LocationService: AMapLocationManagerDelegate { + // 定位更新回调 + func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!, reGeocode: AMapLocationReGeocode!) { + currentLocation = location + currentReGeocode = reGeocode + + print("\(TAG)--------- 定位更新: \(location.coordinate.latitude), \(location.coordinate.longitude)") + + var locationInfo: [String: String] = [ + "user_lat": "\(location.coordinate.latitude)", + "user_lng": "\(location.coordinate.longitude)" + ] + if let regeocode = currentReGeocode { + let address = [ + reGeocode.province, // 省 + reGeocode.city, // 市 + reGeocode.district, // 区 + reGeocode.street, // 街道 + reGeocode.number // 门牌号 + ].compactMap { $0 }.joined(separator: "") + print("\(TAG)--------- 定位地址: \(address)") + locationInfo["user_location"] = address + } + + + NotificationCenter.default.post( + name: .updatelocationEvent, + object: nil, + userInfo: locationInfo + ) + } + + // 定位失败回调 + func amapLocationManager(_ manager: AMapLocationManager!, didFailWithError error: Error!) { + print("\(TAG)--------- 定位失败: \(error.localizedDescription)") + } + + // 权限变更回调 + func amapLocationManager(_ manager: AMapLocationManager!, doRequireLocationAuth locationManager: CLLocationManager!) { + locationManager.requestWhenInUseAuthorization() + } +} diff --git a/local_plugins/location_service/ios/Classes/LocationServicePlugin.swift b/local_plugins/location_service/ios/Classes/LocationServicePlugin.swift new file mode 100644 index 000000000..ad30a36f7 --- /dev/null +++ b/local_plugins/location_service/ios/Classes/LocationServicePlugin.swift @@ -0,0 +1,44 @@ +import Flutter +import UIKit +import AMapLocationKit + +public class LocationServicePlugin: NSObject, FlutterPlugin { + private static var channel: FlutterMethodChannel? + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "com.yunqiinnovation.location_service", binaryMessenger: registrar.messenger()) + let instance = LocationServicePlugin() + // 必须先设置隐私合规 + DispatchQueue.main.async { + // 2. API Key + AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03" + AMapServices.shared().enableHTTPS = true + // 3. 打印验证(调试用) + print("高德SDK初始化完成") + } + 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) + default: + result(FlutterMethodNotImplemented) + } + } + + private func addstartlocationmonitor(){ + // 通常放在启动后或模块初始化时 + NotificationCenter.default.addObserver( + forName: .locationstartEvent, + object: nil, + queue: .main + ) { notification in + print("📥 收到 启动定位服务事件") + LocationService.shared.initialize() + } + } +} diff --git a/local_plugins/location_service/ios/location_service.podspec b/local_plugins/location_service/ios/location_service.podspec index b5409ce22..549e6c473 100644 --- a/local_plugins/location_service/ios/location_service.podspec +++ b/local_plugins/location_service/ios/location_service.podspec @@ -13,17 +13,15 @@ A new Flutter project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'location_service/Sources/location_service/**/*' + s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '12.0' + s.dependency 'AMapLocation', '~> 2.10.0' - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.static_framework = true + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' + } + s.platform = :ios, '12.0' s.swift_version = '5.0' - - # If your plugin requires a privacy manifest, for example if it uses any - # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your - # plugin's privacy impact, and then uncomment this line. For more information, - # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - # s.resource_bundles = {'location_service_privacy' => ['location_service/Sources/location_service/PrivacyInfo.xcprivacy']} end diff --git a/local_plugins/location_service/ios/location_service/Package.swift b/local_plugins/location_service/ios/location_service/Package.swift deleted file mode 100644 index 0b0dd0fbd..000000000 --- a/local_plugins/location_service/ios/location_service/Package.swift +++ /dev/null @@ -1,32 +0,0 @@ -// swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "location_service", - platforms: [ - .iOS("12.0") - ], - products: [ - .library(name: "location-service", targets: ["location_service"]) - ], - dependencies: [], - targets: [ - .target( - name: "location_service", - dependencies: [], - resources: [ - // If your plugin requires a privacy manifest, for example if it uses any required - // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's - // privacy impact, and then uncomment these lines. For more information, see - // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - // .process("PrivacyInfo.xcprivacy"), - - // If you have other resources that need to be bundled with your plugin, refer to - // the following instructions to add them: - // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package - ] - ) - ] -) 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 deleted file mode 100644 index 1b6f10f28..000000000 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Flutter -import UIKit - -public class LocationServicePlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "location_service", binaryMessenger: registrar.messenger()) - let instance = LocationServicePlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("iOS " + UIDevice.current.systemVersion) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy b/local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy deleted file mode 100644 index a34b7e2e6..000000000 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyAccessedAPITypes - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/local_plugins/location_service/lib/location_service.dart b/local_plugins/location_service/lib/location_service.dart index c662819c6..a760675ce 100644 --- a/local_plugins/location_service/lib/location_service.dart +++ b/local_plugins/location_service/lib/location_service.dart @@ -11,33 +11,8 @@ class LocationService { 'com.yunqiinnovation.location_service', ); - // 事件通道 - 简化为只使用数据和状态通道 - late final EventChannel _dataEventChannel; - late final EventChannel _statusEventChannel; - - // 事件流 - late final Stream _dataStream; - late final Stream _statusStream; - - // 获取事件流 - Stream get onDataReceived => _dataStream; - - // 状态流直接传递原始数据 - Stream get onStatusUpdated => _statusStream; - // 内部构造函数 - LocationService._internal() { - _dataEventChannel = const EventChannel( - 'com.yunqiinnovation.music_service/data_events', - ); - _statusEventChannel = const EventChannel( - 'com.yunqiinnovation.music_service/status_events', - ); - - // 初始化事件流 - _dataStream = _dataEventChannel.receiveBroadcastStream(); - _statusStream = _statusEventChannel.receiveBroadcastStream(); - } + LocationService._internal() {} Future> location() async { final result = await _methodChannel.invokeMethod('location'); diff --git a/local_plugins/location_service/pubspec.yaml b/local_plugins/location_service/pubspec.yaml index b575ce3b7..a7809ba9f 100644 --- a/local_plugins/location_service/pubspec.yaml +++ b/local_plugins/location_service/pubspec.yaml @@ -17,21 +17,8 @@ dev_dependencies: sdk: flutter flutter_lints: ^5.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter packages. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. plugin: platforms: android: @@ -40,33 +27,3 @@ flutter: ios: pluginClass: LocationServicePlugin - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/to/asset-from-package - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/to/font-from-package