Browse Source

上传新版

weicu
liwei1dao 1 year ago
parent
commit
c936b90238
  1. 15
      ios/Podfile.lock
  2. 112
      lib/data/models/user_Info.dart
  3. 4
      lib/data/services/network/api.dart
  4. 43
      lib/modules/devices/controllers/devices_controller.dart
  5. 524
      lib/modules/devices/views/devices_view.dart
  6. 76
      lib/modules/pairing/controllers/pairing_controller.dart
  7. 1
      lib/modules/splash/controllers/splash_controller.dart
  8. 6
      local_plugins/chat_api/ios/chat_api/Package.swift
  9. 258
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  10. 38
      local_plugins/location_service/ios/.gitignore
  11. 131
      local_plugins/location_service/ios/Classes/LocationService.swift
  12. 44
      local_plugins/location_service/ios/Classes/LocationServicePlugin.swift
  13. 27
      local_plugins/location_service/ios/location_service.podspec
  14. 21
      local_plugins/location_service/ios/location_service/Package.swift
  15. 7
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationCallback.swift
  16. 16
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift
  17. 357
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift
  18. 72
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift
  19. 3
      local_plugins/location_service/pubspec.yaml

15
ios/Podfile.lock

@ -3,8 +3,6 @@ PODS:
- AMapNavi (~> 10.0.900) - AMapNavi (~> 10.0.900)
- Flutter - Flutter
- AMapFoundation (1.8.2) - AMapFoundation (1.8.2)
- AMapLocation (2.10.0):
- AMapFoundation (>= 1.8.0)
- AMapNavi (10.0.900): - AMapNavi (10.0.900):
- AMapFoundation (>= 1.8.2) - AMapFoundation (>= 1.8.2)
- audio_waveforms (0.0.1): - audio_waveforms (0.0.1):
@ -56,9 +54,6 @@ PODS:
- libwebp/sharpyuv (1.5.0) - libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0): - libwebp/webp (1.5.0):
- libwebp/sharpyuv - libwebp/sharpyuv
- location_service (0.0.1):
- AMapLocation (~> 2.10.0)
- Flutter
- Mantle (2.2.0): - Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0) - Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0) - Mantle/extobjc (2.2.0)
@ -85,7 +80,7 @@ PODS:
- SDWebImage/Core (~> 5.17) - SDWebImage/Core (~> 5.17)
- spotify_sdk (0.0.1): - spotify_sdk (0.0.1):
- Flutter - Flutter
- tencentcloud_cos_sdk_plugin (1.2.3): - tencentcloud_cos_sdk_plugin (1.2.4):
- Flutter - Flutter
- QCloudCOSXML (= 6.4.7) - QCloudCOSXML (= 6.4.7)
- WechatOpenSDK-XCFramework (2.0.4) - WechatOpenSDK-XCFramework (2.0.4)
@ -105,7 +100,6 @@ DEPENDENCIES:
- fluwx (from `.symlinks/plugins/fluwx/ios`) - fluwx (from `.symlinks/plugins/fluwx/ios`)
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`) - haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`) - keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
- location_service (from `.symlinks/plugins/location_service/ios`)
- ota (from `.symlinks/plugins/ota/ios`) - ota (from `.symlinks/plugins/ota/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`) - record_darwin (from `.symlinks/plugins/record_darwin/ios`)
@ -116,7 +110,6 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- AMapFoundation - AMapFoundation
- AMapLocation
- AMapNavi - AMapNavi
- FBAEMKit - FBAEMKit
- FBSDKCoreKit - FBSDKCoreKit
@ -156,8 +149,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/haptic_feedback/ios" :path: ".symlinks/plugins/haptic_feedback/ios"
keyboard_height_plugin: keyboard_height_plugin:
:path: ".symlinks/plugins/keyboard_height_plugin/ios" :path: ".symlinks/plugins/keyboard_height_plugin/ios"
location_service:
:path: ".symlinks/plugins/location_service/ios"
ota: ota:
:path: ".symlinks/plugins/ota/ios" :path: ".symlinks/plugins/ota/ios"
permission_handler_apple: permission_handler_apple:
@ -174,7 +165,6 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b
AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec
AMapLocation: 5248aec2455ebb5d104b367813c946430a2ee033
AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
classic_bluetooth: 29f392f1ab7c52862a9ef036c94f2713b02e0c50 classic_bluetooth: 29f392f1ab7c52862a9ef036c94f2713b02e0c50
@ -192,7 +182,6 @@ SPEC CHECKSUMS:
haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0 haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0
keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05 keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
location_service: fab4ef23d473296a215c49e12c129e466574d94e
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
ota: 20c3e522c57e7ba70ce118109e73650acc9fec76 ota: 20c3e522c57e7ba70ce118109e73650acc9fec76
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
@ -203,7 +192,7 @@ SPEC CHECKSUMS:
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca
tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b tencentcloud_cos_sdk_plugin: 7bed564dbe72df23e7b5cacb1e51b1a20cb1639c
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
wifi_iot: f645260a2be8608517b2a9bf4c39b98e97003acc wifi_iot: f645260a2be8608517b2a9bf4c39b98e97003acc

112
lib/data/models/user_Info.dart

@ -1,3 +1,5 @@
import 'dart:ffi';
class User { class User {
// 单例实例 // 单例实例
static User? _instance; static User? _instance;
@ -21,15 +23,7 @@ class User {
String phonemac; String phonemac;
String password; String password;
int viplv = 0; //添加会员等级字段,0表示普通用户 int viplv = 0; //添加会员等级字段,0表示普通用户
String earphonename; List<UserDevice> devices;
String earphonemac;
String earphonelicense;
String earphoneauthcode;
// final Int64 lastbettime;
// final Int64 createtime;
// final String wxopenid;
// final String googleopenid;
// final String facebookopenid;
// 私有构造函数 // 私有构造函数
User._({ User._({
@ -42,21 +36,14 @@ class User {
required this.phone, required this.phone,
required this.phonemac, required this.phonemac,
required this.password, required this.password,
required this.viplv, // 添加默认值 required this.viplv,
required this.earphonename, required this.devices,
required this.earphonemac,
required this.earphonelicense,
required this.earphoneauthcode,
// required this.lastbettime,
// required this.createtime,
// required this.wxopenid,
// required this.googleopenid,
// required this.facebookopenid,
}); });
factory User.fromJson(Map<String, dynamic> json) { factory User.fromJson(Map<String, dynamic> json) {
// 修复1:安全获取嵌套的 user 字段 // 修复1:安全获取嵌套的 user 字段
final userData = json['user'] as Map<String, dynamic>?; // 允许空值 final userData = json['user'] as Map<String, dynamic>?; // 允许空值
final devicesData = json['devices'] as Map<String, dynamic>?; // 允许空值
//final token = json['data']['token'] as String? ?? ''; //final token = json['data']['token'] as String? ?? '';
//final token = ''; //final token = '';
// 更新单例实例 // 更新单例实例
@ -74,21 +61,17 @@ class User {
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonename: (userData?['earphonename'] as String?) ?? '', devices: (devicesData as List<dynamic>?)
earphonemac: (userData?['earphonemac'] as String?) ?? '', ?.map((e) => UserDevice.fromJson(e as Map<String, dynamic>))
earphonelicense: (userData?['earphonelicense'] as String?) ?? '', .toList() ??
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', [], // 处理设备列表
// lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '',
// googleopenid: (userData?['googleopenid'] as String?) ?? '',
// facebookopenid: (userData?['facebookopenid'] as String?) ?? '',
); );
} }
factory User.fronTokenJson(Map<String, dynamic> json) { factory User.fronTokenJson(Map<String, dynamic> json) {
// 修复1:安全获取嵌套的 user 字段 // 修复1:安全获取嵌套的 user 字段
final userData = json['user'] as Map<String, dynamic>?; // 允许空值 final userData = json['user'] as Map<String, dynamic>?; // 允许空值
print("数据获取fronTokenJson${userData}"); final devicesData = json['devices'] as Map<String, dynamic>?; // 允许空值
print("数据获取fronTokenJson${userData} ${devicesData}");
// 更新单例实例 // 更新单例实例
return _instance = User._( return _instance = User._(
token: '', token: '',
@ -101,17 +84,12 @@ class User {
phone: (userData?['phone'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonename: (userData?['earphonename'] as String?) ?? '', devices: devicesData != null
earphonemac: (userData?['earphonemac'] as String?) ?? '', ? (devicesData as List<dynamic>)
earphonelicense: (userData?['earphonelicense'] as String?) ?? '', .map((e) => UserDevice.fromJson(e as Map<String, dynamic>))
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', .toList()
// lastbettime: _parseTimestamp(userData['lastbettime']), : [], // 处理设备列表
// createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '',
// googleopenid: (userData?['googleopenid'] as String?) ?? '',
// facebookopenid: (userData?['facebookopenid'] as String?) ?? '',
); );
} }
@ -121,7 +99,6 @@ class User {
'name': name, 'name': name,
'uid': uid, 'uid': uid,
'avatar': avatar, 'avatar': avatar,
'language': language, 'language': language,
'token': token, 'token': token,
'mail': mail, 'mail': mail,
@ -129,17 +106,52 @@ class User {
'phonemac': phonemac, 'phonemac': phonemac,
'password': password, 'password': password,
'viplv': viplv, 'viplv': viplv,
'devices': devices.map((device) => device.toJson()).toList(),
},
};
// 添加设备
addUserDevice(UserDevice device) {
devices.add(device);
}
// 查找设备
removeUserDevice(int id) {
devices.removeWhere((device) => device.id == id);
}
}
class UserDevice {
int id;
String uid;
int devicetype;
String devicename;
String devicemac;
String license;
UserDevice({
required this.id,
required this.uid,
required this.devicetype,
required this.devicename,
required this.devicemac,
required this.license,
});
'earphonename': earphonename, factory UserDevice.fromJson(Map<String, dynamic> json) => UserDevice(
'earphonemac': earphonemac, id: json['id'] as int,
"earphonelicense": earphonelicense, uid: json['uid'] as String,
'earphoneauthcode': earphoneauthcode, devicetype: (json['devicetype'] as num).toInt(),
devicename: json['devicename'] as String,
devicemac: json['devicemac'] as String,
license: json['license'] as String,
);
// 'lastbettime': lastbettime, toJson() => {
// 'createtime': createtime, 'id': id,
// 'wxopenid': wxopenid, 'uid': uid,
// 'googleopenid': googleopenid, 'devicetype': devicetype,
// 'facebookopenid': facebookopenid, 'devicename': devicename,
} 'devicemac': devicemac,
'license': license,
}; };
} }

4
lib/data/services/network/api.dart

@ -137,10 +137,10 @@ class Api {
} }
//绑定设备 //绑定设备
static bindearphone([params]) { static binddevice([params]) {
return DioManager().request( return DioManager().request(
NWMethod.post, NWMethod.post,
'/api/home/user_bindearphone', '/api/home/user_binddevice',
params: params, params: params,
); );
} }

43
lib/modules/devices/controllers/devices_controller.dart

@ -1,3 +1,5 @@
import 'dart:ffi';
import '/core/utils/logger.dart'; import '/core/utils/logger.dart';
import '/data/models/user_Info.dart'; import '/data/models/user_Info.dart';
import '/data/services/ble_manager.dart'; import '/data/services/ble_manager.dart';
@ -12,50 +14,33 @@ import 'package:get/get_state_manager/src/simple/get_controllers.dart';
*/ */
class DevicesController extends GetxController { class DevicesController extends GetxController {
late final BleManager _bleManager; late final BleManager _bleManager;
//耳机绑定 late final Map<int, List<UserDevice>> devices;
final earphonestate = 0.obs;
final earphoneName = "".obs;
final earphoneMac = "".obs;
final earphoneLicense = "".obs;
final earphoneCode = "".obs;
//录音笔
final voicepenstate = 0.obs;
final isvoicepen = false.obs;
final voicepenName = "".obs;
final voicepenMac = "".obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_bleManager = Get.find<BleManager>(); _bleManager = Get.find<BleManager>();
earphoneName.value = User.instance.earphonename;
earphoneMac.value = User.instance.earphonemac;
earphoneLicense.value = User.instance.earphonelicense;
earphoneCode.value = User.instance.earphoneauthcode;
if (earphoneCode.value != "") {
earphonestate.value = 1;
}
if (earphoneLicense.value != "") {
earphonestate.value = 2;
}
} }
@override @override
void onClose() { void onClose() {
super.onClose(); super.onClose();
for (var device in User.instance.devices) {
if (!devices.containsKey(device.id)) {
devices[device.devicetype] = [];
} else {
devices[device.devicetype]!.add(device);
}
}
} }
void unbindearphone() async { void unbindearphone(UserDevice device) async {
final response = await Api.unbindearphone({ final response = await Api.unbindearphone({
"code": earphoneLicense.value, "id": device.id,
"earphonname": earphoneMac.value,
}); });
if (response != null) { if (response != null) {
User.instance.earphonename = ""; devices[device.devicetype]!.remove(device);
User.instance.earphonemac = ""; User.instance.removeUserDevice(device.id);
User.instance.earphonelicense = "";
earphonestate.value = 0;
_bleManager.clearPairedDevices(); _bleManager.clearPairedDevices();
_bleManager.disconnect(); _bleManager.disconnect();
Get.toNamed(Routes.pairing); Get.toNamed(Routes.pairing);

524
lib/modules/devices/views/devices_view.dart

@ -4,286 +4,328 @@ import '/modules/devices/controllers/devices_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '/core/theme/app_colors.dart';
import '/data/models/user_Info.dart';
import '../controllers/devices_controller.dart';
// 设备类型枚举
enum DeviceType {
Unknown(0, "未知设备"),
Classic_bluetooth_Headset(1, "经典蓝牙耳机"),
BLE_Bluetooth_Headset(2, "BLE蓝牙耳机"),
Smart_Voice_Recorder(3, "智能录音笔"),
SmartEye(4, "智能眼镜");
const DeviceType(this.value, this.displayName);
final int value;
final String displayName;
static DeviceType fromValue(int value) {
return DeviceType.values.firstWhere(
(type) => type.value == value,
orElse: () => DeviceType.Unknown,
);
}
IconData get icon {
switch (this) {
case DeviceType.Classic_bluetooth_Headset:
case DeviceType.BLE_Bluetooth_Headset:
return Icons.headphones;
case DeviceType.Smart_Voice_Recorder:
return Icons.mic;
case DeviceType.SmartEye:
return Icons.visibility;
default:
return Icons.device_unknown;
}
}
}
class DevicesView extends GetView<DevicesController> { class DevicesView extends GetView<DevicesController> {
const DevicesView({Key? key}) : super(key: key); const DevicesView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 直接使用 Theme.of(context) 获取当前主题
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.white,
appBar: AppBar( body: Column(
automaticallyImplyLeading: false, children: [
centerTitle: true, // 顶部标题栏
elevation: 0, Container(
backgroundColor: isDarkMode ? Colors.black : Colors.white, padding: EdgeInsets.only(
leading: IconButton( top: MediaQuery.of(context).padding.top + 10.h,
icon: Icon( left: 20.w,
Icons.arrow_back_ios, right: 20.w,
size: 20.sp, bottom: 20.h,
color: isDarkMode ? Colors.white : Colors.black, ),
decoration: BoxDecoration(
color: isDarkMode ? Colors.black : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
),
SizedBox(width: 16.w),
Text(
"deviceManagement".tr,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
),
), ),
onPressed: () { // 设备列表内容
Get.back(); // 正常返回 Expanded(
}, child: _buildDevicesList(isDarkMode),
),
title: Text(
'deviceManagement'.tr,
style: TextStyle(
fontSize: 20.sp,
color: isDarkMode ? Colors.white : Colors.black,
), ),
),
),
body: ListView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
children: [
_buildEarphoneCard(isDarkMode),
SizedBox(height: 20.h),
_buildVoiceRecorderCard(isDarkMode),
SizedBox(height: 20.h),
], ],
), ),
); );
} }
// 耳机 Widget _buildDevicesList(bool isDarkMode) {
Widget _buildEarphoneCard(bool isDarkMode) { final devices = User.instance.devices;
return Column(
crossAxisAlignment: CrossAxisAlignment.start, if (devices.isEmpty) {
return _buildEmptyState(isDarkMode);
}
// 按设备类型分组
final Map<DeviceType, List<UserDevice>> groupedDevices = {};
for (var device in devices) {
final deviceType = DeviceType.fromValue(device.devicetype);
if (!groupedDevices.containsKey(deviceType)) {
groupedDevices[deviceType] = [];
}
groupedDevices[deviceType]!.add(device);
}
return ListView(
padding: EdgeInsets.all(20.w),
children: [ children: [
Padding( // 遍历每个设备类型
padding: EdgeInsets.only(left: 4.w, bottom: 8.h), ...groupedDevices.entries.map((entry) {
child: Text( final deviceType = entry.key;
'earphone'.tr, // 耳机 final deviceList = entry.value;
style: TextStyle(
fontSize: 12.sp, return Column(
fontWeight: FontWeight.w500, crossAxisAlignment: CrossAxisAlignment.start,
color: isDarkMode ? Colors.blue[400] : Colors.blue[600], children: [
letterSpacing: 1, // 设备类型标题
), Padding(
), padding: EdgeInsets.only(bottom: 12.h),
), child: Text(
Container( deviceType.displayName,
decoration: BoxDecoration( style: TextStyle(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, fontSize: 16.sp,
borderRadius: BorderRadius.circular(16.r), fontWeight: FontWeight.w600,
boxShadow: [ color: isDarkMode ? Colors.white : Colors.black87,
BoxShadow( ),
color: isDarkMode ),
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
), ),
// 该类型下的设备列表
...deviceList.map((device) => _buildDeviceCard(
device: device,
deviceType: deviceType,
isDarkMode: isDarkMode,
)),
SizedBox(height: 20.h),
], ],
), );
child: ClipRRect( }).toList(),
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
Obx(
() =>
// 耳机列表
_buildSimpleNavigationSetting(
title: controller.earphonestate.value != 0
? controller.earphoneName.value
: "notBound".tr, // 未绑定
subtitle: controller.earphonestate.value != 0
? controller.earphoneMac.value
: "deviceNotBoundYet".tr, // 还未绑定设备
icon: Icons.headphones,
iconBgColor: isDarkMode
? const Color.fromARGB(255, 255, 255, 255)!
.withOpacity(0.3)
: const Color.fromARGB(255, 0, 0, 0)!,
iconColor: isDarkMode
? const Color.fromARGB(255, 0, 0, 0)!
: const Color.fromARGB(255, 255, 255, 255)!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
isDarkMode: isDarkMode,
showbutt: controller.earphonestate == 2,
onPressed: () {
controller.unbindearphone();
},
),
)
],
),
),
),
),
], ],
); );
} }
// 录音笔 Widget _buildEmptyState(bool isDarkMode) {
Widget _buildVoiceRecorderCard(bool isDarkMode) { return Center(
return Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Padding( Icon(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h), Icons.devices_other,
child: Text( size: 80.sp,
'voicerecorder'.tr, //AI设置 color: isDarkMode ? Colors.white54 : Colors.grey[400],
),
SizedBox(height: 20.h),
Text(
"暂未绑定任何设备",
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 16.sp,
fontWeight: FontWeight.w500, color: isDarkMode ? Colors.white70 : Colors.grey[600],
color: isDarkMode ? Colors.blue[400] : Colors.blue[600],
letterSpacing: 1,
), ),
), ),
), SizedBox(height: 12.h),
Container( Text(
decoration: BoxDecoration( "请先绑定设备后再进行管理",
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, style: TextStyle(
borderRadius: BorderRadius.circular(16.r), fontSize: 14.sp,
boxShadow: [ color: isDarkMode ? Colors.white54 : Colors.grey[500],
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
// 实时对话入口
Obx(
() => _buildSimpleNavigationSetting(
title: 'notBound'.tr, // 未绑定
subtitle: 'deviceNotBoundYet'.tr, // 还未绑定设备
icon: Icons.edit,
iconBgColor: isDarkMode
? const Color.fromARGB(255, 255, 255, 255)!
.withOpacity(0.3)
: const Color.fromARGB(255, 0, 0, 0)!,
iconColor: isDarkMode
? const Color.fromARGB(255, 0, 0, 0)!
: const Color.fromARGB(255, 255, 255, 255)!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
isDarkMode: isDarkMode,
showbutt: controller.isvoicepen.value,
onPressed: () {},
),
)
],
),
), ),
), ),
), ],
], ),
); );
} }
// 简单导航设置项 Widget _buildDeviceCard({
Widget _buildSimpleNavigationSetting({ required UserDevice device,
required String title, required DeviceType deviceType,
String? subtitle,
required IconData icon,
required Color iconBgColor,
required Color iconColor,
Color? titleColor,
Color? subtitleColor,
required bool isDarkMode, required bool isDarkMode,
required bool showbutt,
required VoidCallback onPressed,
}) { }) {
return InkWell( return Container(
child: Padding( margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
child: Row( decoration: BoxDecoration(
mainAxisAlignment: MainAxisAlignment.spaceBetween, color: isDarkMode ? AppColors.cardBackground : Colors.white,
children: [ borderRadius: BorderRadius.circular(12.r),
Expanded( boxShadow: [
child: Row( BoxShadow(
children: [ color: Colors.black.withOpacity(0.05),
Container( blurRadius: 8,
width: 32.w, offset: const Offset(0, 2),
height: 32.w, ),
decoration: BoxDecoration( ],
color: iconBgColor, ),
borderRadius: BorderRadius.circular(16.r), child: Row(
), children: [
child: Icon( // 设备图标
icon, Container(
size: 16.w, width: 48.w,
color: iconColor, height: 48.w,
), decoration: BoxDecoration(
color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
deviceType.icon,
color: isDarkMode ? Colors.white : Colors.black87,
size: 24.sp,
),
),
SizedBox(width: 16.w),
// 设备信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
device.devicename.isNotEmpty ? device.devicename : "未知设备",
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
SizedBox(height: 4.h),
Text(
device.devicemac.isNotEmpty ? device.devicemac : "无MAC地址",
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600],
), ),
SizedBox(width: 12.w), ),
Flexible( if (device.license.isNotEmpty) ...[
child: Column( SizedBox(height: 4.h),
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: [ "授权码: ${device.license}",
Text( style: TextStyle(
title, fontSize: 12.sp,
style: TextStyle( color: isDarkMode ? Colors.white54 : Colors.grey[500],
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: titleColor ??
(isDarkMode ? Colors.white : Colors.black87),
),
overflow: TextOverflow.ellipsis,
),
if (subtitle != null) SizedBox(height: 2.h),
if (subtitle != null)
Text(
subtitle,
style: TextStyle(
fontSize: 12.sp,
color: subtitleColor ??
(isDarkMode
? Colors.white70
: Colors.black54),
),
overflow: TextOverflow.ellipsis,
),
],
), ),
), ),
], ],
],
),
),
// 解绑按钮
GestureDetector(
onTap: () => _showUnbindDialog(device, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 6.h,
),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(6.r),
border: Border.all(
color: Colors.red.withOpacity(0.3),
width: 1,
),
),
child: Text(
"unbind".tr,
style: TextStyle(
fontSize: 12.sp,
color: Colors.red,
fontWeight: FontWeight.w500,
),
), ),
), ),
if (showbutt) ),
Container( ],
width: 74.w, ),
height: 40.w, );
child: ElevatedButton( }
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, void _showUnbindDialog(UserDevice device, bool isDarkMode) {
shape: RoundedRectangleBorder( Get.dialog(
borderRadius: BorderRadius.circular(12), AlertDialog(
), backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 2), title: Text(
), "确认解绑",
onPressed: onPressed, style: TextStyle(
child: Text( color: isDarkMode ? Colors.white : Colors.black87,
'unbind'.tr, // 解绑 ),
style: TextStyle( ),
color: Colors.white, content: Text(
), "确定要解绑设备 \"${device.devicename}\" 吗?",
), style: TextStyle(
)), color: isDarkMode ? Colors.white70 : Colors.black54,
], ),
), ),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
"取消",
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.grey[600],
),
),
),
TextButton(
onPressed: () {
Get.back();
controller.unbindearphone(device);
},
child: const Text(
"确认解绑",
style: TextStyle(color: Colors.red),
),
),
],
), ),
); );
} }

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

@ -589,11 +589,12 @@ class PairingController extends GetxController {
//自动绑定设备 0 未合作商 1 合作商 2,已绑定 //自动绑定设备 0 未合作商 1 合作商 2,已绑定
Future<int> authbindingDevice(BluetoothDevice device) async { Future<int> authbindingDevice(BluetoothDevice device) async {
if (User.instance.earphoneauthcode != "" && // if (User.instance.earphoneauthcode != "" &&
User.instance.earphonemac == device.address) { // User.instance.earphonemac == device.address) {
// 已经绑定 // // 已经绑定
return 9; // return 9;
} // }
// 检查设备名称是否包含 DeepSound 或 DeapSound // 检查设备名称是否包含 DeepSound 或 DeapSound
final deviceName = device.name; final deviceName = device.name;
@ -632,13 +633,13 @@ class PairingController extends GetxController {
_verifiedDeviceAddress = uuids; _verifiedDeviceAddress = uuids;
print('找到匹配设备,获取到 uuids: $uuids'); print('找到匹配设备,获取到 uuids: $uuids');
// 检查是否已经绑定 // 检查是否已经绑定
if (User.instance.earphonelicense != "" && // if (User.instance.earphonelicense != "" &&
User.instance.earphonemac == device.address) { // User.instance.earphonemac == device.address) {
// 已经绑定 // // 已经绑定
return 10; // return 10;
} else { // } else {
return 1; return 1;
} // }
} }
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
// Android设备,提取15-24字节的授权码 // Android设备,提取15-24字节的授权码
@ -649,13 +650,13 @@ class PairingController extends GetxController {
deviceCode.value = code; deviceCode.value = code;
_verifiedDeviceAddress = device.address; _verifiedDeviceAddress = device.address;
// 检查是否已经绑定 // 检查是否已经绑定
if (User.instance.earphonelicense != "" && // if (User.instance.earphonelicense != "" &&
User.instance.earphonemac == device.address) { // User.instance.earphonemac == device.address) {
// 已经绑定 // // 已经绑定
return 10; // return 10;
} else { // } else {
return 1; return 1;
} // }
} }
} else { } else {
break; break;
@ -729,19 +730,21 @@ class PairingController extends GetxController {
//确认绑定设备 //确认绑定设备
void confirmBindDevice() async { void confirmBindDevice() async {
final response = await Api.bindearphone({ final response = await Api.binddevice({
"code": deviceCode.value, "code": deviceCode.value,
"earphonname": currentDevice?.name, "devicename": currentDevice?.name,
"earphonemac": currentDevice?.address, // 使用 address 替代 deviceId "devicemac": currentDevice?.address, // 使用 address 替代 deviceId
}); });
Logger.i(_tag, '手动授权结果 ${response}'); Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) { if (response != null) {
Logger.i(_tag, '绑定设备成功'); Logger.i(_tag, '绑定设备成功 $response');
User.instance.earphonemac = currentDevice?.address ?? ''; // 使用 address if (response['device'] != '') {
User.instance.earphonename = currentDevice?.name ?? ''; // 根据API设计调整
User.instance.earphonelicense = deviceCode.value; final device = UserDevice.fromJson(response['device']);
_transition(PairingState.verifying); User.instance.addUserDevice(device);
_verifyDevice(_currentClassicDevice!); _transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!);
}
return; return;
} else { } else {
Logger.e(_tag, '绑定设备失败!'); Logger.e(_tag, '绑定设备失败!');
@ -777,9 +780,9 @@ class PairingController extends GetxController {
}); });
Logger.i(_tag, '手动授权结果 ${response}'); Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) { if (response != null) {
User.instance.earphonemac = currentDevice?.address ?? ''; // 使用 address // User.instance.earphonemac = currentDevice?.address ?? ''; // 使用 address
User.instance.earphonename = currentDevice?.name ?? ''; // User.instance.earphonename = currentDevice?.name ?? '';
User.instance.earphoneauthcode = codeInputController.text.trim(); // User.instance.earphoneauthcode = codeInputController.text.trim();
_transition(PairingState.verifying); _transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!); _verifyDevice(_currentClassicDevice!);
} else { } else {
@ -859,7 +862,16 @@ class PairingController extends GetxController {
void onSkipBindingTap() { void onSkipBindingTap() {
Logger.i(_tag, '用户选择临时跳过手动绑定'); Logger.i(_tag, '用户选择临时跳过手动绑定');
User.instance.earphonemac = currentDevice?.address ?? ''; User.instance.addUserDevice(
UserDevice(
id: 0, // 临时设备ID
uid: User.instance.uid,
devicetype: 1,
devicemac: _currentClassicDevice?.address ?? '',
devicename: _currentClassicDevice?.name ?? 'Unknown Device',
license: "", // 临时授权码
),
);
_transition(PairingState.verifying); _transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!); _verifyDevice(_currentClassicDevice!);
} }

1
lib/modules/splash/controllers/splash_controller.dart

@ -122,6 +122,7 @@ class SplashController extends GetxController {
print('getLoginToken:进来了'); print('getLoginToken:进来了');
// 根据API设计调整 // 根据API设计调整
final user = User.fromJson(response); final user = User.fromJson(response);
// 存储用户信息(示例) // 存储用户信息(示例)
await _storage.write('user_info', user.toJson()); await _storage.write('user_info', user.toJson());
return true; return true;

6
local_plugins/chat_api/ios/chat_api/Package.swift

@ -14,14 +14,16 @@ let package = Package(
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/MacPaw/OpenAI.git", from: "0.4.3"), .package(url: "https://github.com/MacPaw/OpenAI.git", from: "0.4.3"),
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.9.0") .package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.9.0"),
.package(path: "../../../location_service/ios/location_service"),
], ],
targets: [ targets: [
.target( .target(
name: "chat_api", name: "chat_api",
dependencies: [ dependencies: [
.product(name: "MCP", package: "swift-sdk"), .product(name: "MCP", package: "swift-sdk"),
.product(name: "OpenAI", package: "OpenAI") .product(name: "OpenAI", package: "OpenAI"),
.product(name: "location-service", package: "location_service")
], ],
exclude: [] exclude: []
), ),

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

@ -1,12 +1,11 @@
import Foundation import Foundation
import UIKit import UIKit
import Contacts
import EventKit import EventKit
import CoreLocation import CoreLocation
import MediaPlayer import MessageUI
import AVFoundation import location_service
/// 操作结果 /// 结果结构体
public struct Result { public struct Result {
let success: Bool let success: Bool
let message: String let message: String
@ -20,26 +19,14 @@ public struct Result {
/// 系统功能处理器 /// 系统功能处理器
public class SystemFunctionHandler: NSObject { public class SystemFunctionHandler: NSObject {
// 位置管理器
private var locationManager: CLLocationManager?
private var locationCompletion: ((Result) -> Void)?
// 事件存储 // 事件存储
private var eventStore: EKEventStore? private var eventStore: EKEventStore?
public override init() { public override init() {
super.init() super.init()
setupLocationManager()
setupEventStore() setupEventStore()
} }
/// 设置位置管理器
private func setupLocationManager() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
}
/// 设置事件存储 /// 设置事件存储
private func setupEventStore() { private func setupEventStore() {
eventStore = EKEventStore() eventStore = EKEventStore()
@ -67,7 +54,7 @@ public class SystemFunctionHandler: NSObject {
registerGetCurrentTimeFunction(client: client) registerGetCurrentTimeFunction(client: client)
// 注册获取当前位置函数 // 注册获取当前位置函数
// registerGetCurrentLocationFunction(client: client) registerGetCurrentLocationFunction(client: client)
// 注册媒体控制函数 // 注册媒体控制函数
registerMediaPlayFunction(client: client) registerMediaPlayFunction(client: client)
@ -112,8 +99,8 @@ public class SystemFunctionHandler: NSObject {
let handler = TranslationModeHandler() let handler = TranslationModeHandler()
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "enter_translation_mode", name: "translation_mode",
description: "用户请求进入实时翻译模式时,启动实时翻译功能", description: "进入翻译模式",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
@ -130,7 +117,7 @@ public class SystemFunctionHandler: NSObject {
], ],
"message": [ "message": [
"type": "string", "type": "string",
"description": "短信内容" "description": "要发送的短信内容"
] ]
], ],
"required": ["contact", "message"] "required": ["contact", "message"]
@ -182,7 +169,7 @@ public class SystemFunctionHandler: NSObject {
], ],
"time": [ "time": [
"type": "string", "type": "string",
"description": "提醒时间,格式为'yyyy-MM-dd HH:mm',如'2023-12-31 14:30'" "description": "提醒时间,格式:yyyy-MM-dd HH:mm"
] ]
], ],
"required": ["title", "time"] "required": ["title", "time"]
@ -191,7 +178,7 @@ public class SystemFunctionHandler: NSObject {
let handler = SetReminderHandler(eventStore: eventStore) let handler = SetReminderHandler(eventStore: eventStore)
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "set_reminder", name: "set_reminder",
description: "设置提醒事项", description: "设置提醒",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
@ -204,7 +191,7 @@ public class SystemFunctionHandler: NSObject {
"properties": [ "properties": [
"format": [ "format": [
"type": "string", "type": "string",
"description": "时间格式,可选,默认为标准格式" "description": "时间格式,可选"
] ]
], ],
"required": [] "required": []
@ -213,7 +200,7 @@ public class SystemFunctionHandler: NSObject {
let handler = GetCurrentTimeHandler() let handler = GetCurrentTimeHandler()
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "get_current_time", name: "get_current_time",
description: "获取当前日期和时间", description: "获取当前时间",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
@ -227,16 +214,16 @@ public class SystemFunctionHandler: NSObject {
"required": [] "required": []
] ]
let handler = GetCurrentLocationHandler(systemHandler: self) let handler = GetCurrentLocationHandler()
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "get_current_location", name: "get_current_location",
description: "获取当前地理位置", description: "获取当前位置",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
} }
/// 注册媒体播放功能 /// 注册媒体播放函数
private func registerMediaPlayFunction(client: MCPClient) { private func registerMediaPlayFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
@ -253,7 +240,7 @@ public class SystemFunctionHandler: NSObject {
) )
} }
/// 注册媒体暂停功能 /// 注册媒体暂停函数
private func registerMediaPauseFunction(client: MCPClient) { private func registerMediaPauseFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
@ -270,7 +257,7 @@ public class SystemFunctionHandler: NSObject {
) )
} }
/// 注册媒体上一首功能 /// 注册媒体上一首函数
private func registerMediaPreviousFunction(client: MCPClient) { private func registerMediaPreviousFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
@ -287,7 +274,7 @@ public class SystemFunctionHandler: NSObject {
) )
} }
/// 注册媒体下一首功能 /// 注册媒体下一首函数
private func registerMediaNextFunction(client: MCPClient) { private func registerMediaNextFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
@ -304,7 +291,7 @@ public class SystemFunctionHandler: NSObject {
) )
} }
/// 注册打开录音机功能 /// 注册打开录音机函数
private func registerOpenRecorderFunction(client: MCPClient) { private func registerOpenRecorderFunction(client: MCPClient) {
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"type": "object", "type": "object",
@ -315,122 +302,72 @@ public class SystemFunctionHandler: NSObject {
let handler = OpenRecorderHandler() let handler = OpenRecorderHandler()
_ = client.registerLocalFunction( _ = client.registerLocalFunction(
name: "open_recorder", name: "open_recorder",
description: "打开系统录音机并开始录音", description: "打开录音机",
parameters: parameters, parameters: parameters,
handler: handler handler: handler
) )
} }
}
// MARK: - 功能处理器实现
/// 获取当前位置处理器
private class GetCurrentLocationHandler: FunctionHandler {
// MARK: - 内部方法 func handle(arguments: [String: Any]) async throws -> String {
// 检查位置权限
/// 获取当前位置 guard LocationService.shared.hasLocationPermission() else {
internal func getCurrentLocation() async -> Result { let errorResult: [String: Any] = [
return await withCheckedContinuation { continuation in "success": false,
locationCompletion = { result in "message": "位置权限被拒绝。请前往 设置 > 隐私与安全 > 定位服务 > Deep Voice 启用定位权限"
continuation.resume(returning: result) ]
} let jsonData = try JSONSerialization.data(withJSONObject: errorResult)
return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}"
}
// 先尝试获取缓存的位置数据
if let cachedLocation = LocationService.shared.getCachedLocationData() {
let message = "当前位置:\(cachedLocation.address ?? "未知地址")(纬度 \(cachedLocation.latitude),经度 \(cachedLocation.longitude))"
// 设置15秒超时 // 异步触发最新定位(不等待结果)
DispatchQueue.global().asyncAfter(deadline: .now() + 15.0) { [weak self] in Task {
if self?.locationCompletion != nil { do {
self?.locationCompletion?(Result(success: false, message: "位置请求超时")) _ = try await LocationService.shared.getCurrentLocation()
self?.locationCompletion = nil } catch {
print("后台更新位置失败: \(error)")
} }
} }
// 检查位置服务是否可用 let result: [String: Any] = [
guard CLLocationManager.locationServicesEnabled() else { "success": true,
locationCompletion?(Result(success: false, message: "位置服务未启用。请前往 设置 > 隐私与安全 > 定位服务 启用定位服务")) "message": message
locationCompletion = nil ]
return let jsonData = try JSONSerialization.data(withJSONObject: result)
} return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}"
// 确保locationManager已初始化
guard let locationManager = locationManager else {
locationCompletion?(Result(success: false, message: "位置管理器未初始化"))
locationCompletion = nil
return
}
// 检查位置权限
let authStatus: CLAuthorizationStatus
if #available(iOS 14.0, *) {
authStatus = locationManager.authorizationStatus
} else {
authStatus = CLLocationManager.authorizationStatus()
}
print("当前位置权限状态: \(authStatus.rawValue)")
switch authStatus {
case .notDetermined:
print("请求位置权限...")
// 请求权限
locationManager.requestWhenInUseAuthorization()
// 权限结果会在 didChangeAuthorization 回调中处理
case .denied:
locationCompletion?(Result(success: false, message: "位置权限被拒绝。请前往 设置 > 隐私与安全 > 定位服务 > Deep Voice 启用定位权限"))
locationCompletion = nil
case .restricted:
locationCompletion?(Result(success: false, message: "位置权限受限制,可能是由于家长控制或企业配置限制"))
locationCompletion = nil
case .authorizedWhenInUse, .authorizedAlways:
print("已有位置权限,开始获取位置...")
// 已有权限,直接请求位置
locationManager.requestLocation()
@unknown default:
locationCompletion?(Result(success: false, message: "未知的位置权限状态: \(authStatus.rawValue)"))
locationCompletion = nil
}
}
}
}
// MARK: - CLLocationManagerDelegate
extension SystemFunctionHandler: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
locationCompletion?(Result(success: false, message: "无法获取位置信息"))
return
}
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude
let message = "当前位置:纬度 \(latitude),经度 \(longitude)"
locationCompletion?(Result(success: true, message: message))
locationCompletion = nil
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationCompletion?(Result(success: false, message: "获取位置失败:\(error.localizedDescription)"))
locationCompletion = nil
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// 只有在有位置请求等待时才处理权限变化
guard locationCompletion != nil else {
return
} }
switch status { // 如果没有缓存数据,同步获取位置
case .authorizedWhenInUse, .authorizedAlways: do {
manager.requestLocation() let locationData = try await LocationService.shared.getCurrentLocation()
case .denied, .restricted: let message = "当前位置:\(locationData.address ?? "未知地址")(纬度 \(locationData.latitude),经度 \(locationData.longitude))"
locationCompletion?(Result(success: false, message: "位置权限被拒绝。请前往 设置 > 隐私与安全 > 定位服务 > Deep Voice 启用定位权限"))
locationCompletion = nil let result: [String: Any] = [
case .notDetermined: "success": true,
// 权限仍未确定,等待用户操作 "message": message
break ]
@unknown default: let jsonData = try JSONSerialization.data(withJSONObject: result)
locationCompletion?(Result(success: false, message: "位置权限状态异常,请重试")) return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}"
locationCompletion = nil } catch {
let errorResult: [String: Any] = [
"success": false,
"message": "获取位置失败:\(error.localizedDescription)"
]
let jsonData = try JSONSerialization.data(withJSONObject: errorResult)
return String(data: jsonData, encoding: .utf8) ?? "{\"error\": \"JSON序列化失败\"}"
} }
} }
} }
// MARK: - 功能处理器实现
/// 退出交互处理器 /// 退出交互处理器
private class ExitInteractionHandler: FunctionHandler { private class ExitInteractionHandler: FunctionHandler {
func handle(arguments: [String: Any]) async throws -> String { func handle(arguments: [String: Any]) async throws -> String {
@ -460,22 +397,23 @@ private class SendTextMessageHandler: FunctionHandler {
} }
private func sendTextMessage(contact: String, message: String) async -> Result { private func sendTextMessage(contact: String, message: String) async -> Result {
guard let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else {
return Result(success: false, message: "无法创建短信URL")
}
return await withCheckedContinuation { continuation in return await withCheckedContinuation { continuation in
DispatchQueue.main.async { DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(url) { if MFMessageComposeViewController.canSendText() {
UIApplication.shared.open(url) { success in let messageVC = MFMessageComposeViewController()
if success { messageVC.recipients = [contact]
continuation.resume(returning: Result(success: true, message: "已打开短信应用")) messageVC.body = message
} else {
continuation.resume(returning: Result(success: false, message: "打开短信应用失败")) if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
} let window = windowScene.windows.first,
let rootViewController = window.rootViewController {
rootViewController.present(messageVC, animated: true)
continuation.resume(returning: Result(success: true, message: "短信界面已打开"))
} else {
continuation.resume(returning: Result(success: false, message: "无法打开短信界面"))
} }
} else { } else {
continuation.resume(returning: Result(success: false, message: "无法打开短信应用")) continuation.resume(returning: Result(success: false, message: "设备不支持发送短信"))
} }
} }
} }
@ -591,34 +529,16 @@ private class GetCurrentTimeHandler: FunctionHandler {
private func getCurrentTime(format: String?) -> Result { private func getCurrentTime(format: String?) -> Result {
let formatter = DateFormatter() let formatter = DateFormatter()
if let format = format, !format.isEmpty { if let customFormat = format, !customFormat.isEmpty {
formatter.dateFormat = format formatter.dateFormat = customFormat
} else { } else {
formatter.dateStyle = .full formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeStyle = .full
formatter.locale = Locale(identifier: "zh_CN")
} }
let currentTime = formatter.string(from: Date()) formatter.locale = Locale(identifier: "zh_CN")
return Result(success: true, message: currentTime) let timeString = formatter.string(from: Date())
}
}
/// 获取当前位置处理器
private class GetCurrentLocationHandler: FunctionHandler {
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("系统处理器未初始化")
// }
let result: Result = await systemHandler.getCurrentLocation() return Result(success: true, message: timeString)
return "{\"result\": \"\(result.message)\", \"success\": \(result.success)}"
} }
} }
@ -666,4 +586,4 @@ private class OpenRecorderHandler: FunctionHandler {
return "{\"result\": \"无法打开录音机应用\"}" return "{\"result\": \"无法打开录音机应用\"}"
} }
} }

38
local_plugins/location_service/ios/.gitignore

@ -1,38 +0,0 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

131
local_plugins/location_service/ios/Classes/LocationService.swift

@ -1,131 +0,0 @@
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()
}
}

44
local_plugins/location_service/ios/Classes/LocationServicePlugin.swift

@ -1,44 +0,0 @@
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()
}
}
}

27
local_plugins/location_service/ios/location_service.podspec

@ -1,27 +0,0 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint location_service.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'location_service'
s.version = '0.0.1'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'AMapLocation', '~> 2.10.0'
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'
end

21
local_plugins/location_service/ios/location_service/Package.swift

@ -0,0 +1,21 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "location_service",
platforms: [
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(name: "location-service", targets: ["location_service"])
],
dependencies: [ ],
targets: [
.target(
name: "location_service",
dependencies: [],
path: "Sources/location_service"
)
]
)

7
local_plugins/location_service/ios/location_service/Sources/location_service/LocationCallback.swift

@ -0,0 +1,7 @@
import Foundation
/// 定位回调协议
public protocol LocationCallback: AnyObject {
func onLocationSuccess(latitude: Double, longitude: Double, address: String)
func onLocationError(errorCode: Int, errorMessage: String)
}

16
local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift

@ -0,0 +1,16 @@
import Foundation
/// 位置数据结构
public struct LocationData: Codable {
public let latitude: Double
public let longitude: Double
public let address: String
public let timestamp: TimeInterval
public init(latitude: Double, longitude: Double, address: String, timestamp: TimeInterval) {
self.latitude = latitude
self.longitude = longitude
self.address = address
self.timestamp = timestamp
}
}

357
local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift

@ -0,0 +1,357 @@
import Foundation
import CoreLocation
import UIKit
public extension Notification.Name {
static let locationStartEvent = Notification.Name("locationStartEvent")
static let updateLocationEvent = Notification.Name("updateLocationEvent")
}
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 currentLocationCallback: ((Result<[String: Any], Error>) -> Void)?
// 存储权限请求回调(Flutter用)
private var permissionRequestCallback: ((Result<[String: Bool], Error>) -> Void)?
// 存储服务间调用的回调
private var serviceCallbacks: [LocationCallback] = []
// 缓存的位置数据
private var cachedLocationData: LocationData?
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()
}
}
}
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]))
return
}
permissionRequestCallback = completion
locationManager?.requestWhenInUseAuthorization()
}
/// 按需获取当前位置(Flutter用)
public func getCurrentLocation(completion: @escaping (Result<[String: Any], Error>) -> Void) {
if !checkPermissions() {
completion(.failure(NSError(domain: "LocationService", code: -2, userInfo: [NSLocalizedDescriptionKey: "需要定位权限"])))
return
}
if !initialized {
completion(.failure(NSError(domain: "LocationService", code: -3, userInfo: [NSLocalizedDescriptionKey: "定位服务未初始化"])))
return
}
if currentLocationCallback != nil {
completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "正在定位中,请稍后再试"])))
return
}
// 保存回调,开始定位
currentLocationCallback = completion
locationManager?.requestLocation()
}
// MARK: - 供其他iOS服务使用的接口
/// 检查是否有定位权限(供其他服务使用)
public func hasLocationPermission() -> Bool {
return checkPermissions()
}
/// 异步获取当前位置(供其他iOS服务使用)
public func getCurrentLocation() async throws -> LocationData {
return try await withCheckedThrowingContinuation { continuation in
getCurrentLocation { 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)
} else {
continuation.resume(throwing: NSError(domain: "LocationService", code: -5, userInfo: [NSLocalizedDescriptionKey: "位置数据格式错误"]))
}
case .failure(let error):
continuation.resume(throwing: 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()
}
}
/// 同步获取缓存的位置信息(供其他服务使用)
/// 注意:这个方法返回的可能是过期的位置信息
public func getCachedLocationData() -> LocationData? {
// 优先返回内存中的缓存
if let cached = cachedLocationData {
return cached
}
// 从UserDefaults中读取缓存
return loadCachedLocationFromUserDefaults()
}
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 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
}
let address = UserDefaults.standard.string(forKey: "address") ?? "未知地址"
let timestampDouble = UserDefaults.standard.double(forKey: "timestamp")
if timestampDouble == 0 { return nil }
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)")
// 进行逆地理编码获取地址
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()
}
} else {
let errorMessage = error?.localizedDescription ?? "未知错误"
print("\(LocationService.TAG): 定位失败: \(errorMessage)")
// 通知Flutter
if let callback = flutterCallback {
callback(.failure(NSError(domain: "LocationService", code: -1, userInfo: [NSLocalizedDescriptionKey: "定位失败: \(errorMessage)"])))
}
// 通知其他iOS服务
serviceCallbacks.forEach { callback in
callback.onLocationError(errorCode: -1, errorMessage: errorMessage)
}
// 清空服务回调列表
serviceCallbacks.removeAll()
}
}
}
// MARK: - CLLocationManagerDelegate
extension LocationService: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
handleLocationResult(location, error: nil)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
handleLocationResult(nil, error: error)
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("\(LocationService.TAG): 定位权限状态变更: \(status.rawValue)")
switch status {
case .authorizedWhenInUse, .authorizedAlways:
if !initialized {
initialize()
}
permissionRequestCallback?(.success(["granted": true]))
permissionRequestCallback = nil
case .denied, .restricted:
permissionRequestCallback?(.success(["granted": false]))
permissionRequestCallback = nil
case .notDetermined:
break
@unknown default:
break
}
}
}

72
local_plugins/location_service/ios/location_service/Sources/location_service/LocationServicePlugin.swift

@ -0,0 +1,72 @@
import Foundation
#if canImport(Flutter)
import Flutter
import UIKit
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()
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 {
case .success(let locationInfo):
result(locationInfo)
case .failure(let error):
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

3
local_plugins/location_service/pubspec.yaml

@ -26,4 +26,5 @@ flutter:
pluginClass: LocationServicePlugin pluginClass: LocationServicePlugin
ios: ios:
pluginClass: LocationServicePlugin pluginClass: LocationServicePlugin
swiftPackage:
path: ios/location_service

Loading…
Cancel
Save