Browse Source

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

weicu
fdp 1 year ago
parent
commit
f8f95c5baf
  1. 296
      lib/modules/agent/views/agent_view.dart
  2. 287
      lib/modules/home/views/home_view.dart
  3. 2
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  4. 82
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
  5. 39
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt
  6. 45
      local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift
  7. 155
      local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift

296
lib/modules/agent/views/agent_view.dart

@ -196,7 +196,17 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
IntroStepBuilder(
group: 'ai',
order: 3,
getOverlayPosition: IntroOverlayBuilder.overlayPosition,
getOverlayPosition: ({
required Offset offset,
required Size screenSize,
required Size size,
}) {
return OverlayPosition(
top: 200.h,
width: screenSize.width,
crossAxisAlignment: CrossAxisAlignment.end,
);
},
overlayBuilder: (params) {
return Obx(
() => IntroOverlayBuilder.homeBuilder(
@ -242,8 +252,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
? IntroStepBuilder(
group: 'ai',
order: 4,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
@ -405,8 +413,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(height: 12.h),
],
);
} else if (controller.isTextInputMode.value &&
!controller.hasTextInput.value) {
} else {
// 文本输入模式下,且输入框为空
return Column(
children: [
@ -449,8 +456,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(height: 12.h),
],
);
} else {
return const SizedBox.shrink();
}
},
),
@ -506,8 +511,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return IntroStepBuilder(
group: 'ai',
order: 1,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
@ -670,19 +673,30 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
: Colors.blue[100])
: (isDarkMode
? Colors.grey[800]
: Colors.grey[200]),
: const Color.fromARGB(
255, 255, 255, 255)),
borderRadius:
BorderRadius.circular(22.w),
border: Border.all(
color: isPushToTalkActive
? Colors.blue
: (isDarkMode
? Colors.grey[800]!
: Colors.grey
.withOpacity(0.1)),
width:
isPushToTalkActive ? 2.w : 1.w,
),
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 2,
offset: const Offset(0, 1),
),
],
// border: Border.all(
// color: isPushToTalkActive
// ? Colors.blue
// : (isDarkMode
// ? Colors.grey[800]!
// : const Color.fromARGB(
// 255, 255, 255, 255)
// .withOpacity(0.1)),
// width:
// isPushToTalkActive ? 2.w : 1.w,
// ),
),
child: Row(
children: [
@ -742,143 +756,149 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(width: 8.w),
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮
IntroStepBuilder(
group: 'ai',
order: 2,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}',
alignment: false,
onTap: () async {
await controller
.noviceGuideService.aiStepsList[
controller.noviceGuideService
.aiStep]['method']();
params.onNext!();
controller.sendMessage();
},
);
},
builder: (BuildContext context, GlobalKey key) =>
Obx(
key: key,
() {
final hasText = controller.hasTextInput.value;
final hasImage =
controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage;
final shouldShowExpand =
controller.shouldShowExpandButton.value;
Builder(builder: (context) {
StepWidgetParams? widgetParams;
return IntroStepBuilder(
group: 'ai',
order: 2,
onHighlightWidgetTap: () async {
await controller
.noviceGuideService.aiStepsList[
controller.noviceGuideService.aiStep]
['method']();
widgetParams!.onNext!();
controller.sendMessage();
},
overlayBuilder: (params) {
widgetParams = params;
return IntroOverlayBuilder.aiBuilder(
params,
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}',
alignment: false,
);
},
builder:
(BuildContext context, GlobalKey key) =>
Obx(
key: key,
() {
final hasText =
controller.hasTextInput.value;
final hasImage =
controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage;
final shouldShowExpand =
controller.shouldShowExpandButton.value;
if (shouldShowSend) {
// 显示发送按钮(可能还有展开按钮)
return Column(
children: [
// 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText)
Padding(
padding:
EdgeInsets.only(bottom: 40.h),
child: GestureDetector(
onTap: () {
// 打开底部弹出编辑模式
controller.openFullScreenEdit();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor:
Colors.transparent,
builder: (context) =>
const FullScreenEditor(),
).then((_) {
// 弹出页关闭时的回调
if (shouldShowSend) {
// 显示发送按钮(可能还有展开按钮)
return Column(
children: [
// 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText)
Padding(
padding:
EdgeInsets.only(bottom: 40.h),
child: GestureDetector(
onTap: () {
// 打开底部弹出编辑模式
controller
.closeFullScreenEdit();
});
},
child: Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[700]
: Colors.grey[300],
borderRadius:
BorderRadius.circular(
16.w),
),
child: Icon(
Icons.expand_less,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 20.sp,
.openFullScreenEdit();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor:
Colors.transparent,
builder: (context) =>
const FullScreenEditor(),
).then((_) {
// 弹出页关闭时的回调
controller
.closeFullScreenEdit();
});
},
child: Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[700]
: Colors.grey[300],
borderRadius:
BorderRadius.circular(
16.w),
),
child: Icon(
Icons.expand_less,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 20.sp,
),
),
),
),
// 发送按钮
GestureDetector(
onTap: () =>
controller.sendMessage(),
child: Container(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: primaryColor,
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
Icons.send,
color: Colors.white,
size: 22.sp,
),
),
),
// 发送按钮
GestureDetector(
onTap: () => controller.sendMessage(),
],
);
} else {
// 显示'+'选择按钮
return Obx(() {
final isExpanded = controller
.isPlusOptionsExpanded.value;
return GestureDetector(
onTap: () {
controller.togglePlusOptions();
},
child: Container(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: primaryColor,
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
Icons.send,
color: Colors.white,
isExpanded
? Icons.close
: Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22.sp,
),
),
),
],
);
} else {
// 显示'+'选择按钮
return Obx(() {
final isExpanded = controller
.isPlusOptionsExpanded.value;
return GestureDetector(
onTap: () {
controller.togglePlusOptions();
},
child: Container(
width: 44.w,
height: 44.w,
margin: EdgeInsets.symmetric(
vertical: 2.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius:
BorderRadius.circular(22.w),
),
child: Icon(
isExpanded
? Icons.close
: Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22.sp,
),
),
);
});
}
},
),
),
);
});
}
},
),
);
}),
],
),

287
lib/modules/home/views/home_view.dart

@ -72,86 +72,81 @@ class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
// 预缓存图片
precacheImage(
const AssetImage('assets/images/headphone_new3.png'), context);
precacheImage(
const AssetImage('assets/images/headphone_new4.png'), context);
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFC),
body: SafeArea(
child: Padding(
child: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部标题栏
_buildAppBar(isDarkMode),
// 可变高度的顶部图片区域 - 使用Expanded让它占用剩余空间
Expanded(
child: Column(
children: [
Obx(() => _buildAdaptiveEarbudsImage()),
SizedBox(height: 20.h),
// 设备状态卡片
IntroStepBuilder(
group: 'home',
order: 1,
onWidgetLoad: () {
controller.noviceGuideService.startHomeGuide();
},
getOverlayPosition: IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
'欢迎使用Voitrans!首先,让我们唤醒你的专属 AI—— 小言',
onNext: () {
controller.noviceGuideService.startWakeupListen();
},
);
},
builder: (BuildContext context, GlobalKey key) =>
SizedBox(
key: key,
child: _buildAnimatedDeviceStatusCard(isDarkMode),
),
),
SizedBox(height: 20.h),
IntroStepBuilder(
group: 'home',
order: 2,
getOverlayPosition: IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return Obx(
() => IntroOverlayBuilder.homeBuilder(
params,
controller.noviceGuideService.isWakeup.value,
onNext: () {
controller.noviceGuideService.closeWakeupListen();
},
),
);
// 顶部图片
_buildEarbudsImage(),
SizedBox(height: 20.h),
// 设备状态卡片
IntroStepBuilder(
group: 'home',
order: 1,
onWidgetLoad: () {
controller.noviceGuideService.startHomeGuide();
},
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
'欢迎使用Voitrans!首先,让我们唤醒你的专属 AI—— 小言',
onNext: () {
controller.noviceGuideService.startWakeupListen();
},
);
},
builder: (BuildContext context, GlobalKey key) => SizedBox(
key: key,
child: _buildDeviceStatusCard(isDarkMode),
),
),
SizedBox(height: 20.h),
IntroStepBuilder(
group: 'home',
order: 2,
getOverlayPosition: ({
required Offset offset,
required Size screenSize,
required Size size,
}) {
return OverlayPosition(
top: 200.h,
width: screenSize.width,
crossAxisAlignment: CrossAxisAlignment.end,
);
},
overlayBuilder: (params) {
return Obx(
() => IntroOverlayBuilder.homeBuilder(
params,
controller.noviceGuideService.isWakeup.value,
onNext: () {
controller.noviceGuideService.closeWakeupListen();
},
builder: (BuildContext context, GlobalKey key) =>
SizedBox(
key: key,
),
),
],
);
},
builder: (BuildContext context, GlobalKey key) => SizedBox(
key: key,
),
),
// 热门AI体验 - 固定在底部,基于内容的固定高度
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, // 重要:让高度基于内容
children: [
_buildSectionTitle(isDarkMode),
SizedBox(height: 16.h),
// 给AI布局一个固定的高度,或者让它基于内容自适应
_buildPremiumAILayout(),
],
),
// 热门AI体验
_buildSectionTitle(isDarkMode),
SizedBox(height: 16.h),
_buildPremiumAILayout(),
],
),
),
@ -201,47 +196,70 @@ class HomeView extends GetView<HomeController> {
);
}
// 自适应高度的耳机图片 - 根据设备连接状态动态调整高度
// 在自适应图片组件中添加RepaintBoundary
Widget _buildAdaptiveEarbudsImage() {
// 耳机图片
Widget _buildEarbudsImage() {
final bool isDarkMode = Get.isDarkMode;
final double imageHeight =
controller.isFullyConnectedRx.value ? 160.h : 220.h;
return RepaintBoundary(
// 添加重绘边界,优化动画性能
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
height: imageHeight,
child: PageView(
children: [
Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: Image.asset(
isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png',
height: imageHeight,
fit: BoxFit.contain,
),
return SizedBox(
height: 220.h,
child: PageView(
children: [
// Center(
// child: ClipRRect(
// // 添加 ClipRRect 来实现圆角
// borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小
// child: SizedBox(
// width: double.infinity,
// height: 200.h,
// child: Image.asset(
// isDarkMode
// ? 'assets/images/robot_light.png'
// : 'assets/images/robot_light.png',
// fit: BoxFit.fitWidth,
// ),
// ),
// ),
// ),
Center(
child: ClipRRect(
// 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r),
child: Image.asset(
isDarkMode
? 'assets/images/headphone_new3.png'
: 'assets/images/headphone_new4.png',
height: 220.h,
fit: BoxFit.contain,
),
),
],
),
),
// Center(
// child: ClipRRect(
// // 添加 ClipRRect
// borderRadius: BorderRadius.circular(16.r),
// child: SizedBox(
// width: double.infinity,
// height: 200.h,
// child: Image.asset(
// isDarkMode
// ? 'assets/images/glasses_light.png'
// : 'assets/images/glasses_light.png',
// fit: BoxFit.fitWidth,
// ),
// ),
// ),
// ),
],
),
);
}
// 带动画的设备状态卡片 - 实现平滑的高度变化过渡
Widget _buildAnimatedDeviceStatusCard(bool isDarkMode) {
// 设备状态卡片
Widget _buildDeviceStatusCard(bool isDarkMode) {
return Obx(() {
// 使用连接状态作为key,强制在状态改变时重建widget
final isConnected = controller.isFullyConnectedRx.value;
return AnimatedContainer(
duration: const Duration(milliseconds: 500), // 平滑过渡动画
curve: Curves.easeInOut,
return Container(
key: ValueKey('device_status_$isConnected'),
decoration: BoxDecoration(
color: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white,
borderRadius: BorderRadius.circular(16.r),
@ -255,58 +273,30 @@ class HomeView extends GetView<HomeController> {
),
padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 16.h),
child: Column(
mainAxisSize: MainAxisSize.min, // 重要:让容器高度自适应内容
children: [
// 状态标题行
_buildDeviceStatusHeader(isDarkMode),
// 设备信息 - 使用 AnimatedSwitcher 实现内容切换动画
// 设备信息
SizedBox(height: 12.h),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Container(
key: ValueKey('device_info_$isConnected'),
child: isConnected
? _buildConnectedDeviceInfo(isDarkMode)
: _buildDisconnectedInfo(isDarkMode),
),
),
// 连接按钮 - 使用 AnimatedSwitcher 实现按钮切换动画
Obx(() {
// 移除连接失败和连接中的状态判断,直接根据是否完全连接来判断
// 使用响应式访问确保UI更新
return controller.isFullyConnectedRx.value
? _buildConnectedDeviceInfo(isDarkMode) //已连接设备信息
: _buildDisconnectedInfo(isDarkMode); //未连接提示
}),
// 连接按钮(仅在未连接时显示)
SizedBox(height: 12.h),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Container(
key: ValueKey('device_button_$isConnected'),
child: !isConnected
? _buildConnectButton()
: _buildDeviceButton(isDarkMode),
),
),
Obx(() {
// 使用响应式访问确保UI更新
if (!controller.isFullyConnectedRx.value) {
return _buildConnectButton();
} else {
return _buildDeviceButton(isDarkMode);
}
}),
],
),
);
@ -740,15 +730,14 @@ class HomeView extends GetView<HomeController> {
child: IntroStepBuilder(
group: 'home',
order: 3,
getOverlayPosition: IntroOverlayBuilder.overlayPosition,
onHighlightWidgetTap: () {
Intro.of(Get.context!).dispose();
controller.exploreAIFeatures();
},
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
'太棒了!你已成功唤醒小言,接下来试试让它帮你做件事吧',
onTap: () {
params.onFinish();
controller.exploreAIFeatures();
},
);
},
builder: (BuildContext context, GlobalKey key) => SizedBox(

2
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -1487,7 +1487,7 @@ class AudioPlayer {
private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated)
// 等待音效超时时间(秒)- 改为 var 以支持动态修改
private var awaitTimeoutSeconds: TimeInterval = 10.0
private var awaitTimeoutSeconds: TimeInterval = 30.0
private var resourcesBundle: Bundle {
#if SWIFT_PACKAGE

82
local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift

@ -131,39 +131,63 @@ private class QQMusicSearchHandler: FunctionHandler {
}
return await withCheckedContinuation { continuation in
// 直接调用QPlayAutoManager的搜索接口
QPlayAutoManager.shared.search(keyword: keyword, searchType: .song) { items in
var result: [String: Any]
if let items = items, !items.isEmpty {
let songs = items.map { item -> [String: Any] in
return [
"mid": item.mid ?? "",
"name": item.name ?? "",
"artist": item.singer?.name ?? "",
"album": item.album ?? "",
"duration": item.duration,
"pic": item.coverUri ?? ""
// 调用更新后的搜索接口,包含错误处理
QPlayAutoManager.shared.search(
keyword: keyword,
searchType: .song,
completion: { items in
var result: [String: Any]
if let items = items, !items.isEmpty {
let songs = items.map { item -> [String: Any] in
return [
"mid": item.mid ?? "",
"name": item.name ?? "",
"artist": item.singer?.name ?? "",
"album": item.album ?? "",
"duration": item.duration,
"pic": item.coverUri ?? ""
]
}
result = [
"success": true,
"message": "Found \(items.count) songs",
"broadcast": false,
"data": songs
]
} else {
result = ["success": false, "error": "No songs found"]
}
result = [
"success": true,
"message": "Found \(items.count) songs and started playing",
"broadcast":false,
"data": songs
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
continuation.resume(returning: jsonString)
} catch {
continuation.resume(returning: "{\"success\": false, \"error\": \"JSON serialization failed\"}")
}
},
onError: { error in
// 处理搜索错误
var errorMessage = "Search failed"
if let error = error {
errorMessage = "Search failed: \(error.localizedDescription)"
}
let result: [String: Any] = [
"success": false,
"error": errorMessage
]
} else {
result = ["success": false, "error": "No songs found"]
}
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
continuation.resume(returning: jsonString)
} catch {
continuation.resume(returning: "{\"success\": false, \"error\": \"JSON serialization failed\"}")
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
continuation.resume(returning: jsonString)
} catch {
continuation.resume(returning: "{\"success\": false, \"error\": \"Search failed and JSON serialization failed\"}")
}
}
}
)
}
}
}

39
local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt

@ -141,12 +141,15 @@ object LocationService {
callback.onLocationError(-1, "定位服务未初始化")
return
}
if (!checkPermissions(ctx)) {
callback.onLocationError(-2, "没有定位权限")
val p = checkPermissions(ctx)
if (p == 1) {
callback.onLocationError(-2, "定位权限没有")
return
}
if (p == 2) {
callback.onLocationError(-2, "后台定位权限没有")
return
}
if (!initialized) {
callback.onLocationError(-3, "定位服务未初始化")
return
@ -238,7 +241,7 @@ object LocationService {
override fun run() {
val ctx = appcontext ?: return
val hasPermission = checkPermissions(ctx)
if (hasPermission && !initialized) {
if (hasPermission ==0 && !initialized) {
Log.d(TAG, "权限已授予,重新初始化定位服务")
initialize(ctx)
stopPermissionMonitor()
@ -256,12 +259,26 @@ object LocationService {
permissionRetryRunnable = null
}
private fun checkPermissions(context: Context): Boolean {
return if (isAppInForeground(context)) {
hasForegroundLocationPermission(context)
} else {
hasBackgroundLocationPermission(context)
/**
* 检查位置权限状态并返回对应编码
* @return 0:已获得所需全部权限;1:未获得前台权限;2:前台权限已获得但后台权限未授予(仅Android 10+)
*/
private fun checkPermissions(context: Context): Int {
// 先检查前台权限是否已授予
val hasForegroundPermission = hasForegroundLocationPermission(context)
if (!hasForegroundPermission) {
return 1 // 前台权限未授予
}
// 前台权限已授予,再根据前后台状态检查后台权限
if (!isAppInForeground(context)) {
// 应用在后台:Android 10+ 需要检查后台权限
return 2 // 后台权限未授予
}
// 所有所需权限均已授予
return 0
}
private fun hasForegroundLocationPermission(context: Context): Boolean {
@ -306,7 +323,7 @@ object LocationService {
val ctx = appcontext ?: activity
// 如果已经有权限,直接返回成功
if (checkPermissions(ctx)) {
if (checkPermissions(ctx) == 0) {
result.success(mapOf("granted" to true))
return
}

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

@ -195,8 +195,15 @@ public class LocationService: NSObject {
// 检查是否已有请求在进行
if self.currentLocationCallback != nil {
completion(.failure(NSError(domain: "LocationService", code: -4, userInfo: [NSLocalizedDescriptionKey: "位置请求正在进行中"])))
return
print("[LocationService] 检测到重复请求,重置之前的请求")
self.resetLocationRequest()
}
// 实时检查权限状态
let currentStatus = CLLocationManager.authorizationStatus()
if currentStatus != self.authorizationStatus {
print("[LocationService] 权限状态已变更,更新缓存")
self.authorizationStatus = currentStatus
}
// 检查权限
@ -205,6 +212,12 @@ public class LocationService: NSObject {
return
}
// 检查位置服务是否启用
if !CLLocationManager.locationServicesEnabled() {
completion(.failure(NSError(domain: "LocationService", code: -6, userInfo: [NSLocalizedDescriptionKey: "位置服务未启用"])))
return
}
// 设置回调
self.currentLocationCallback = { result in
switch result {
@ -228,7 +241,15 @@ public class LocationService: NSObject {
}
}
print("[LocationService] 开始正常定位")
// 设置超时定时器
self.locationTimeoutTimer = Timer.scheduledTimer(withTimeInterval: self.locationTimeout, repeats: false) { [weak self] _ in
print("[LocationService] 位置请求超时")
let callback = self?.currentLocationCallback
self?.resetLocationRequest()
callback?(.failure(NSError(domain: "LocationService", code: -8, userInfo: [NSLocalizedDescriptionKey: "位置请求超时"])))
}
print("[LocationService] 开始正常定位,超时时间: \(self.locationTimeout)秒")
// 使用单次定位
locationManager.requestLocation()
}
@ -253,7 +274,7 @@ public class LocationService: NSObject {
)
let callback = currentLocationCallback
currentLocationCallback = nil
resetLocationRequest() // 使用重置方法
callback?(.success(locationInfo))
}
@ -266,6 +287,18 @@ public class LocationService: NSObject {
"timestamp": locationData.timestamp
]
}
// 添加超时定时器
private var locationTimeoutTimer: Timer?
private let locationTimeout: TimeInterval = 15.0 // 15秒超时
// 添加重置方法
private func resetLocationRequest() {
print("[LocationService] 重置位置请求状态")
currentLocationCallback = nil
locationTimeoutTimer?.invalidate()
locationTimeoutTimer = nil
}
}
// MARK: - CLLocationManagerDelegate
@ -304,7 +337,7 @@ extension LocationService: CLLocationManagerDelegate {
print("[LocationService] 位置获取失败: \(error.localizedDescription)")
let callback = currentLocationCallback
currentLocationCallback = nil
resetLocationRequest() // 使用重置方法
callback?(.failure(error))
}
@ -356,5 +389,3 @@ extension LocationService: CLLocationManagerDelegate {
}
}
// Remove the duplicate LocationData definition from here
// The LocationData struct is already defined in LocationData.swift

155
local_plugins/qq_music/ios/qq_music/Sources/qq_music/QPlayAutoManager.swift

@ -316,7 +316,27 @@ import QPlayAutoSDK
}
// MARK: - 登录状态管理
// 在第206行之前添加新的方法
// Add the error enum at the top of the class
enum QPlayAutoError: Error {
case qqMusicNotInstalled
case loginFailed
case connectionFailed
case searchFailed(String) // 新增搜索失败错误
var localizedDescription: String {
switch self {
case .qqMusicNotInstalled:
return "QQ音乐未安装"
case .loginFailed:
return "登录失败"
case .connectionFailed:
return "连接失败"
case .searchFailed(let reason):
return "搜索失败: \(reason)"
}
}
}
private func ensureQQMusicInstalledAndLoginAndExecute(
_ operation: @escaping () -> Void,
onQQMusicNotInstalled: (() -> Void)? = nil,
@ -330,15 +350,37 @@ import QPlayAutoSDK
}
// QQ音乐已安装,继续检查登录状态
ensureLoginAndExecute(operation, onLoginFailed: onLoginFailed)
ensureLoginAndExecute(operation) { error in
switch error {
case .loginFailed:
onLoginFailed?()
case .qqMusicNotInstalled:
// This shouldn't happen since we already checked above
onQQMusicNotInstalled?()
default:
onLoginFailed?()
}
}
}
// 修改现有的ensureLoginAndExecute方法(第206行开始)
private func ensureLoginAndExecute(_ operation: @escaping () -> Void, onLoginFailed: (() -> Void)? = nil) {
// 修改现有的ensureLoginAndExecute方法
private func ensureLoginAndExecute(
_ operation: @escaping () -> Void,
onFailure: ((QPlayAutoError) -> Void)? = nil
) {
// 首先检查QQ音乐是否已安装
guard isQQMusicInstalled else {
print("QPlayAutoManager: QQ音乐未安装,无法执行操作")
onFailure?(.qqMusicNotInstalled)
return
}
if isLoginOK {
// 已登录,直接执行操作
operation()
} else {
// 未登录,先登录
print("QPlayAutoManager: 未登录,正在尝试登录...")
performLogin { [weak self] success in
@ -347,7 +389,7 @@ import QPlayAutoSDK
operation()
} else {
print("QPlayAutoManager: 登录失败,无法执行操作")
onLoginFailed?()
onFailure?(.loginFailed)
}
}
}
@ -533,33 +575,34 @@ import QPlayAutoSDK
}
@objc public func pause() {
ensureLoginAndExecute {
ensureLoginAndExecute({
QPlayAutoSDK.playerPlayPause()
} onLoginFailed: {
print("QPlayAutoManager: 暂停失败 - 登录失败")
}
}, onFailure: { error in
print("QPlayAutoManager: 暂停失败 - \(error)")
})
}
// Fix seek method (lines 571-578)
@objc public func seek(to position: TimeInterval) {
ensureLoginAndExecute {
QPlayAutoSDK.playerSeek(Int(position))
} onLoginFailed: {
print("QPlayAutoManager: 跳转播放位置失败 - 登录失败")
}
ensureLoginAndExecute {
QPlayAutoSDK.playerSeek(Int(position))
} onFailure: { error in
print("QPlayAutoManager: 跳转播放位置失败 - \(error)")
}
}
// MARK: - 扩展功能方法(需要添加登录检查)
/// 设置播放模式
@objc public func setPlayMode(_ playMode: QPlayAutoPlayMode, completion: @escaping (Bool) -> Void) {
ensureLoginAndExecute {
QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in
completion(success)
}
} onLoginFailed: {
print("QPlayAutoManager: 设置播放模式失败 - 登录失败")
completion(false)
}
ensureLoginAndExecute {
QPlayAutoSDK.setPlayMode(playMode) { (success, dict) in
completion(success)
}
} onFailure: { error in
print("QPlayAutoManager: 设置播放模式失败 - \(error)")
completion(false)
}
}
/// 收藏/取消收藏歌曲
@ -568,11 +611,12 @@ import QPlayAutoSDK
QPlayAutoSDK.setFavoriteStateWithSong(song, isFavorite: isFavorite) { errorCode in
completion(errorCode == 0)
}
} onLoginFailed: {
print("QPlayAutoManager: 设置收藏状态失败 - 登录失败")
completion(false)
}onFailure: { error in
print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(false)
}
}
}
/// 获取相似歌曲
@objc public func requestSimilarSongs(song: QPlayAutoListItem, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) {
@ -585,12 +629,12 @@ import QPlayAutoSDK
completion(nil, error)
}
}
} onLoginFailed: {
print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
}onFailure: { error in
print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
completion(nil, error)
}
}
}
}
/// 获取歌词
@objc public func requestLyric(song: QPlayAutoListItem, completion: @escaping (QPlayAutoLyric?, Error?) -> Void) {
@ -603,12 +647,11 @@ import QPlayAutoSDK
completion(nil, error)
}
}
} onLoginFailed: {
print("QPlayAutoManager: 请求歌词失败 - 登录失败")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
} onFailure: { error in
print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
completion(nil, error)
}
}
}
}
/// 获取歌曲列表
@objc public func requestSonglist(type: QPlayAutoSongListType, completion: @escaping ([QPlayAutoListItem]?, Error?) -> Void) {
@ -621,12 +664,11 @@ import QPlayAutoSDK
completion(nil, error)
}
}
} onLoginFailed: {
print("QPlayAutoManager: 请求歌单失败 - 登录失败")
let error = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "QQ音乐未安装"])
} onFailure: { error in
print("QPlayAutoManager: 请求相似歌曲失败 - 登录失败 - \(error)")
completion(nil, error)
}
}
}
}
/// 播放指定歌曲列表
@objc public func playAtIndex(songList: [QPlayAutoListItem], playIndex: Int, completion: @escaping (Bool) -> Void) {
@ -634,11 +676,11 @@ import QPlayAutoSDK
QPlayAutoSDK.play(atIndex: songList, play: playIndex) { errorCode in
completion(errorCode == 0)
}
} onLoginFailed: {
print("QPlayAutoManager: 播放指定索引歌曲失败 - QQ音乐未安装")
} onFailure: { error in
print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(false)
}
}
}
}
// MARK: - 状态查询
@objc public var isConnected: Bool {
@ -729,17 +771,21 @@ import QPlayAutoSDK
@objc public func search(
keyword: String,
searchType: QPlayAutoSearchType,
completion: @escaping ([QPlayAutoListItem]?) -> Void
completion: @escaping ([QPlayAutoListItem]?) -> Void,
onError: ((Error?) -> Void)? = nil
) {
ensureLoginAndExecute {
QPlayAutoSDK.search(keyword, type: searchType, firstPage: true) { (error, items) in
completion(items)
}
} onLoginFailed: {
print("QPlayAutoManager: 搜索失败 - 登录失败")
completion(nil)
}onFailure: { error in
print("QPlayAutoManager: 搜索失败 - 登录失败 - \(error)")
let loginError = NSError(domain: "QPlayAutoManager", code: -1, userInfo: [
NSLocalizedDescriptionKey: "登录失败: \(error)"
])
onError?(loginError)
}
}
}
@objc public func getItems(
playlistId: String,
@ -756,11 +802,12 @@ import QPlayAutoSDK
completion(nil)
}
}
} onLoginFailed: {
print("QPlayAutoManager: 获取播放列表失败 - 登录失败")
} onFailure: { error in
print("QPlayAutoManager: 设置收藏状态失败 - 登录失败 - \(error)")
completion(nil)
}
}
}
}
// 添加公开的登录方法
@objc public func login(completion: @escaping (Bool) -> Void) {

Loading…
Cancel
Save