Browse Source

上传iOS导航服务代码

weicu
liwei1dao 1 year ago
parent
commit
38c5ab1bd4
  1. 10
      ios/Podfile.lock
  2. 24
      ios/Runner/Info.plist
  3. 42
      local_plugins/amap_service/ios/Classes/AmapHelper.swift
  4. 165
      local_plugins/amap_service/ios/Classes/AmapService.swift
  5. 34
      local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift
  6. 231
      local_plugins/amap_service/ios/Classes/AmapViewController.swift
  7. 16
      local_plugins/amap_service/ios/amap_service.podspec
  8. 10
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

10
ios/Podfile.lock

@ -1,6 +1,10 @@
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):
@ -102,6 +106,8 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- AMapFoundation
- AMapNavi
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
@ -152,7 +158,9 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/tencentcloud_cos_sdk_plugin/ios"
SPEC CHECKSUMS:
amap_service: 3110d959589c6d49570ebe9162418d7b046df30d
amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b
AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec
AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896

24
ios/Runner/Info.plist

@ -76,10 +76,6 @@
<array>
<string>EFAB</string>
</array>
<key>NSAccessorySetupKitSupports</key>
<array>
<string>Bluetooth</string>
</array>
<key>NSAccessorySetupDescriptions</key>
<array>
<dict>
@ -89,7 +85,10 @@
<string>语音助理耳机</string>
</dict>
</array>
<key>NSAccessorySetupKitSupports</key>
<array>
<string>Bluetooth</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
@ -98,7 +97,7 @@
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>mcp.ideapsound.com</key>
<key>mcp.amap.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
@ -107,7 +106,7 @@
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>mcp.amap.com</key>
<key>mcp.ideapsound.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
@ -122,24 +121,27 @@
<string>需要蓝牙权限用于连接和管理音频设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要蓝牙权限用于连接和管理音频设备</string>
<key>NSCalendarsUsageDescription</key>
<string>需要日历权限用于安排和管理您的音频会议和录音任务</string>
<key>NSCameraUsageDescription</key>
<string>需要相机权限用于扫描二维码和视频通话功能</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>语音助手需要位置权限来为您提供基于位置的信息和服务</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>语音助手需要位置权限来为您提供基于位置的信息和服务</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于语音识别和录音功能</string>
<key>NSCalendarsUsageDescription</key>
<string>需要日历权限用于安排和管理您的音频会议和录音任务</string>
<key>NSMotionUsageDescription</key>
<string>我们需要访问您的运动数据以提供更精准的导航服务</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限用于保存录音文件和导入音频文件</string>
<key>NSCameraUsageDescription</key>
<string>需要相机权限用于扫描二维码和视频通话功能</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>audio</string>
<string>location</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>

42
local_plugins/amap_service/ios/Classes/AmapHelper.swift

@ -0,0 +1,42 @@
import Foundation
import NotificationCenter
extension Notification.Name {
static let navigationdEvent = Notification.Name("navigationdEvent") //导航事件
}
@objc class AmapHelper: NSObject {
private static var isInitialized = false
// MARK: - 初始化监听器(你可以在服务底层初始化时调用这个方法)
@objc static func initializeListener() {
guard !isInitialized else {
print("⚠️ AmapHelper 已经初始化过了,忽略重复调用")
return
}
isInitialized = true
print("✅ AmapHelper 初始化监听导航事件")
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNavigationEvent(_:)),
name: .navigationdEvent, // 自定义事件名
object: nil
)
}
// MARK: - 处理导航事件
@objc static func handleNavigationEvent(_ notification: Notification) {
guard let userInfo = notification.userInfo else {
print("⚠️ 导航事件未携带 userInfo")
return
}
print("📍 收到导航事件 userInfo: \(userInfo)")
// 示例处理:读取起点和终点信息
if let start = userInfo["start"] as? String,
let end = userInfo["end"] as? String {
print("🚗 导航从 \(start) 到 \(end)")
// TODO: 调用实际导航逻辑
}
}
}

165
local_plugins/amap_service/ios/Classes/AmapService.swift

@ -0,0 +1,165 @@
import Foundation
import AMapNaviKit
import AVFoundation
public class AmapService: NSObject, AMapNaviDriveManagerDelegate {
public static let shared = AmapService()
private var driveManager: AMapNaviDriveManager?
private var startPoint: AMapNaviPoint?
private var endPoint: AMapNaviPoint?
private var isNavigating = false
private override init() {
super.init()
setupDriveManager()
}
private func setupDriveManager() {
// 使用单例模式初始化
driveManager = AMapNaviDriveManager.sharedInstance()
driveManager?.delegate = self
driveManager?.isUseInternalTTS = true
// 设置高德地图服务
// 1. 设置隐私合规
AMapServices.shared()?.enableHTTPS = true
AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03"
}
public func startNavigation(from start: String, to end: String) {
guard !isNavigating else {
print("导航已在进行中")
return
}
print("开始导航")
// 解析坐标
let startCoords = start.split(separator: ",").compactMap { Double($0.trimmingCharacters(in: .whitespaces)) }
let endCoords = end.split(separator: ",").compactMap { Double($0.trimmingCharacters(in: .whitespaces)) }
guard startCoords.count == 2, endCoords.count == 2 else {
print("坐标格式错误,应为'经度,纬度'")
return
}
// 创建导航点(注意:高德地图使用经纬度顺序)
startPoint = AMapNaviPoint.location(withLatitude: CGFloat(startCoords[1]),
longitude: CGFloat(startCoords[0]))
endPoint = AMapNaviPoint.location(withLatitude: CGFloat(endCoords[1]),
longitude: CGFloat(endCoords[0]))
if let start = startPoint, let end = endPoint {
isNavigating = true
// 使用推荐的默认策略(避让拥堵+速度优先+避免收费)
let strategy: AMapNaviDrivingStrategy = .drivingStrategySingleDefault
// 调用最新API(注意参数是数组)
let success = driveManager?.calculateDriveRoute(withStart: [start],
end: [end],
wayPoints: nil,
drivingStrategy: strategy) ?? false
if !success {
isNavigating = false
print("路径规划参数校验失败")
}
}
}
public func stopNavigation() {
driveManager?.stopNavi()
isNavigating = false
}
// MARK: - AMapNaviDriveManagerDelegate
public func driveManager(onCalculateRouteSuccess driveManager: AMapNaviDriveManager) {
print("Route calculation succeeded")
driveManager.startEmulatorNavi() // 使用模拟导航(测试用)
// 实际使用时改为:
// driveManager.startGPSNavi()
}
public func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
isNavigating = false
print("Route calculation failed: \(error.localizedDescription)")
}
public func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
// 过滤空内容和不需要处理的播报类型
guard !soundString.isEmpty else { return }
switch soundStringType {
case .default:
// 默认导航提示(如"前方500米右转"等)
playSystemTTS(soundString)
case .navigationStart:
// 开始导航提示("开始导航"等)
playSystemTTS("准备出发," + soundString)
case .navigationEnd:
// 到达目的地提示
playSystemTTS(soundString + ",导航结束")
case .yaw:
// 偏航提示("您已偏航,正在重新规划路线"等)
playSystemTTS("注意:" + soundString)
case .interrupt:
// 重要打断播报(如急转弯警告等)
interruptCurrentSpeech()
playSystemTTS(soundString)
default:
// 其他未明确处理的类型使用默认播报
playSystemTTS(soundString)
}
}
public func playSystemTTS(_ text: String) {
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
utterance.rate = 0.5 // 适当放慢语速
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
}
public func interruptCurrentSpeech() {
AVSpeechSynthesizer().stopSpeaking(at: .immediate)
}
public func driveManager(_ driveManager: AMapNaviDriveManager, update naviInfo: AMapNaviInfo?) {
guard let naviInfo = naviInfo else { return }
let currentRoad = naviInfo.currentRoadName ?? "未知道路"
let nextRoad = naviInfo.nextRoadName ?? "未知道路"
let distance = naviInfo.routeRemainDistance
let time = naviInfo.routeRemainTime
print("""
当前道路: \(currentRoad)
下条道路: \(nextRoad)
剩余距离: \(distance)米
剩余时间: \(time)秒
""")
}
public func driveManager(_ driveManager: AMapNaviDriveManager, error: Error) {
isNavigating = false
print("Navigation error occurred: \(error.localizedDescription)")
}
public func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
isNavigating = false
print("Emulator navigation ended")
}
deinit {
driveManager?.stopNavi()
driveManager?.delegate = nil
AMapNaviDriveManager.destroyInstance()
}
}

34
local_plugins/amap_service/ios/Classes/AmapServicePlugin.swift

@ -1,17 +1,45 @@
import Flutter
import UIKit
import AMapNaviKit
public class AmapServicePlugin: NSObject, FlutterPlugin {
private static var channel: FlutterMethodChannel?
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "amap_service", binaryMessenger: registrar.messenger())
let channel = FlutterMethodChannel(name: "com.yunqiinnovation.amap_service", binaryMessenger: registrar.messenger())
let instance = AmapServicePlugin()
// 必须先设置隐私合规
DispatchQueue.main.async {
// 2. API Key
AMapServices.shared()?.apiKey = "aa60a03be9f6531bcc64029e60f95d03"
AMapServices.shared().enableHTTPS = true
// 导航SDK隐私合规(如果使用导航功能)
AMapNaviManagerConfig.shared().updatePrivacyShow(.didShow, privacyInfo: .didContain)
AMapNaviManagerConfig.shared().updatePrivacyAgree(.didAgree)
// 3. 打印验证(调试用)
print("高德SDK初始化完成")
}
registrar.addMethodCallDelegate(instance, channel: channel)
self.channel = channel
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
case "open":
print("liwei------------------ 打开导航页面")
if let args = call.arguments as? [String: Any],
let startpos = args["startpos"] as? String,
let endpos = args["endpos"] as? String,
let ntype = args["ntype"] as? String{
print("liwei------------------ 打开导航页面1 \(startpos) \(endpos)")
DispatchQueue.main.async {
let navVC = AmapViewController(startpos: startpos, endpos: endpos)
navVC.modalPresentationStyle = .fullScreen
UIApplication.shared.keyWindow?.rootViewController?.present(navVC, animated: true)
}
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing 'open' map", details: nil))
}
default:
result(FlutterMethodNotImplemented)
}

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

@ -0,0 +1,231 @@
import UIKit
import AVFAudio
import AMapNaviKit
class AmapViewController: UIViewController {
// MARK: - 必须声明为强引用
private var driveManager: AMapNaviDriveManager!
private var driveView: AMapNaviDriveView!
private let speechSynthesizer = AVSpeechSynthesizer()
private var startPoint: AMapNaviPoint!
private var endPoint: AMapNaviPoint!
// 自定义初始化方法
init(startpos: String, endpos: String) {
super.init(nibName: nil, bundle: nil)
self.startPoint = AmapViewController.parseCoord(from: startpos)
self.endPoint = AmapViewController.parseCoord(from: endpos)
print("导航---------------------: \(startPoint) \(endPoint)")
}
// 解析 "lat,lng" -> AMapNaviPoint
private static func parseCoord(from string: String) -> AMapNaviPoint? {
let components = string.split(separator: ",")
guard components.count == 2,
let lng = Double(components[0]),
let lat = Double(components[1]) else {
print("❌ 无效坐标格式: \(string)")
return nil
}
return AMapNaviPoint.location(withLatitude: lat, longitude: lng)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupAMapComponents()
}
private func setupAMapComponents() {
// 1. 初始化 DriveView(必须在主线程)
print("导航------------------ setupAMapComponents_1")
DispatchQueue.main.async {
print("导航------------------ setupAMapComponents_2")
self.driveView = AMapNaviDriveView(frame: self.view.bounds)
print("导航------------------ setupAMapComponents_3")
self.driveView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
print("导航------------------ setupAMapComponents_4")
self.driveView.delegate = self
print("导航------------------ setupAMapComponents_5")
self.view.addSubview(self.driveView)
print("导航------------------ setupAMapComponents_6")
// 2. 初始化 DriveManager
self.driveManager = AMapNaviDriveManager.sharedInstance()
print("导航------------------ setupAMapComponents_7")
self.driveManager.delegate = self
print("导航------------------ setupAMapComponents_8")
self.driveManager.allowsBackgroundLocationUpdates = true
print("导航------------------ setupAMapComponents_9")
// 3. 确保对象非空再关联
if let manager = self.driveManager, let view = self.driveView {
print("导航------------------ setupAMapComponents_10")
manager.addDataRepresentative(view)
print("导航------------------ setupAMapComponents_11")
self.startNavigation()
print("导航------------------ setupAMapComponents_12")
} else {
print("Error: driveManager or driveView is nil")
}
print("导航------------------ setupAMapComponents_13")
}
}
private func startNavigation() {
// print("导航------------------ startNavigation_1")
// let startPoint = AMapNaviPoint.location(withLatitude: 39.989614, longitude: 116.481763)!
// print("导航------------------ startNavigation_2")
// let endPoint = AMapNaviPoint.location(withLatitude: 39.983456, longitude: 116.315495)!
// print("导航------------------ startNavigation_3")
driveManager.calculateDriveRoute(
withStart: [startPoint],
end: [endPoint],
wayPoints: nil,
drivingStrategy: .drivingStrategySingleDefault
)
print("导航------------------ startNavigation_4")
}
@objc private func exitNavigation() {
print("导航------------------ 退出导航,清理资源")
// 停止导航
driveManager.stopNavi()
driveManager.removeDataRepresentative(driveView)
driveManager.delegate = nil
// 移除导航视图
driveView.delegate = nil
driveView.removeFromSuperview()
driveView = nil
// 销毁导航管理器
driveManager = nil
// 关闭当前页面,视情况而定
// 如果你用的是导航控制器,可以pop
navigationController?.popViewController(animated: true)
// 或者如果是present出来的,dismiss
// dismiss(animated: true, completion: nil)
}
}
// MARK: - 代理方法
extension AmapViewController: AMapNaviDriveViewDelegate, AMapNaviDriveManagerDelegate {
//View回调
func driveViewCloseButtonClicked(_ driveView: AMapNaviDriveView) {
print("导航界面退出按钮被点击")
// 停止导航并清理
driveManager.stopNavi()
driveManager.removeDataRepresentative(driveView)
driveManager.delegate = nil
driveView.delegate = nil
driveView.removeFromSuperview()
// 返回到 Flutter 页面
self.dismiss(animated: true, completion: nil)
}
func driveView(_ driveView: AMapNaviDriveView, didChange showMode: AMapNaviDriveViewShowMode) {
print("导航界面显示模式改变:\(showMode.rawValue)")
}
func driveView(_ driveView: AMapNaviDriveView, didChange trackMode: AMapNaviViewTrackingMode) {
print("导航界面跟随模式改变:\(trackMode.rawValue)")
}
func driveView(_ driveView: AMapNaviDriveView, didChangeOrientation isLandscape: Bool) {
print("导航界面横竖屏切换:isLandscape = \(isLandscape)")
}
func driveView(_ driveView: AMapNaviDriveView, didChangeDayNightType showStandardNightType: Bool) {
print("导航界面白天/黑夜模式切换:isNight = \(showStandardNightType)")
}
func driveViewEdgePadding(_ driveView: AMapNaviDriveView) -> UIEdgeInsets {
// 你可以根据自己 UI 布局返回合适的内边距,防止遮挡
let padding = UIEdgeInsets(top: 100, left: 50, bottom: 80, right: 60)
print("请求导航界面可视区域边距:\(padding)")
return padding
}
//导航回调
func driveManager(_ driveManager: AMapNaviDriveManager, error: Error) {
print("导航------------------ ❌ 发生错误: \(error.localizedDescription)")
}
func driveManager(onCalculateRouteSuccess driveManager: AMapNaviDriveManager){
print("导航------------------ ✅ 路线规划成功(旧方法)")
driveManager.startGPSNavi()
}
func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteSuccessWith type: AMapNaviRoutePlanType) {
print("导航------------------ 路径规划成功,类型: \(type.rawValue)")
driveManager.startGPSNavi()
}
func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
print("导航------------------ ❌ 路线规划失败: \(error.localizedDescription)")
}
func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error, routePlanType type: AMapNaviRoutePlanType) {
print("导航------------------ ❌ 路线规划失败(新方法): \(error.localizedDescription),类型: \(type.rawValue)")
}
func driveManager(_ driveManager: AMapNaviDriveManager, didStartNavi naviMode: AMapNaviMode) {
print("导航------------------ 🚗 已开始导航,模式: \(naviMode.rawValue)")
}
func driveManagerNeedRecalculateRoute(forYaw driveManager: AMapNaviDriveManager) {
print("导航------------------ 出现偏航,需要重新计算路径")
}
func driveManagerNeedRecalculateRoute(forTrafficJam driveManager: AMapNaviDriveManager) {
print("导航------------------ 遇到拥堵,重新规划路线")
}
func driveManager(_ driveManager: AMapNaviDriveManager, onArrivedWayPoint wayPointIndex: Int) {
print("导航------------------ 到达途经点索引:\(wayPointIndex)")
}
func driveManagerIsNaviSoundPlaying(_ driveManager: AMapNaviDriveManager) -> Bool {
print("导航------------------ 🔊 正在判断是否在播报语音")
return false // 如果你有 TTS 系统播报时需返回 true
}
func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
print("导航------------------ 📢 导航播报: \(soundString),类型: \(soundStringType.rawValue)")
// 示例:使用 AVSpeechSynthesizer 播放语音
let utterance = AVSpeechUtterance(string: soundString)
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
speechSynthesizer.speak(utterance)
}
func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
print("导航------------------ 🛑 模拟导航结束")
}
func driveManager(onArrivedDestination driveManager: AMapNaviDriveManager) {
print("导航------------------ 🎯 到达目的地")
}
func driveManager(_ driveManager: AMapNaviDriveManager, onNaviPlay ringType: AMapNaviRingType) {
print("导航------------------ 🔔 播放提示音,类型: \(ringType.rawValue)")
}
func driveManager(_ driveManager: AMapNaviDriveManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
print("导航------------------ 📡 GPS信号强度: \(gpsSignalStrength.rawValue)")
}
func driveManager(_ driveManager: AMapNaviDriveManager, postRouteNotification notifyData: AMapNaviRouteNotifyData) {
print("导航------------------ 📌 路线通知信息: \(notifyData)")
}
func driveManager(_ driveManager: AMapNaviDriveManager, onSuggestChangeMainNaviRoute suggestChangeMainNaviRouteInfo: AMapNaviSuggestChangeMainNaviRouteInfo) {
print("导航------------------ 🔄 建议切换主导航路线: \(suggestChangeMainNaviRouteInfo)")
}
}

16
local_plugins/amap_service/ios/amap_service.podspec

@ -15,15 +15,13 @@ A new Flutter project.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
s.dependency 'AMapNavi', '~> 10.0.900'
# 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 = {'amap_service_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

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

@ -606,18 +606,18 @@ private class GetCurrentTimeHandler: FunctionHandler {
/// 获取当前位置处理器
private class GetCurrentLocationHandler: FunctionHandler {
private weak var systemHandler: SystemFunctionHandler?
private let systemHandler: SystemFunctionHandler
init(systemHandler: SystemFunctionHandler) {
self.systemHandler = systemHandler
}
func handle(arguments: [String: Any]) async throws -> String {
guard let systemHandler = systemHandler else {
throw ChatApiException("系统处理器未初始化")
}
// guard let systemHandler = systemHandler else {
// throw ChatApiException("系统处理器未初始化")
// }
let result = await systemHandler.getCurrentLocation()
let result: Result = await systemHandler.getCurrentLocation()
return "{\"result\": \"\(result.message)\", \"success\": \(result.success)}"
}
}

Loading…
Cancel
Save