diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 3ad7a9657..5f6386cce 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -827,7 +827,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); // 2秒后执行的代码 - openNavigation(start, destination, end_latitude, + openNavigation(false, start, destination, end_latitude, end_longitude, ""); }); } @@ -1865,18 +1865,234 @@ class AgentController extends GetxController with WidgetsBindingObserver { } //去导航 - void openNavigation(String start, String destination, String endlatitude, - String endlongitude, String mode) { + void openNavigation(bool isshow, String start, String destination, + String endlatitude, String endlongitude, String mode) { + print('liwei 导航:$destination $endlatitude $endlongitude $mode'); + if (isshow) { + // 弹出导航选择面板 + _showNavigationSelectionDialog( + start, destination, endlatitude, endlongitude, mode); + } else { + // 使用默认配置直接导航 + _navigateWithDefaultMode( + start, destination, endlatitude, endlongitude, mode); + } + } + + // 显示导航选择底部面板 + void _showNavigationSelectionDialog(String start, String destination, + String endlatitude, String endlongitude, String mode) { + Get.bottomSheet( + Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 顶部指示条 + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + // 标题 + const Padding( + padding: EdgeInsets.all(20), + child: Text( + '选择导航软件', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + // 导航选项列表 + ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.map, color: Colors.blue), + ), + title: const Text('高德地图'), + subtitle: const Text('使用高德地图导航'), + onTap: () { + Get.back(); + openGaodeMap(destination, endlatitude, endlongitude, mode); + }, + ), + ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.map, color: Colors.green), + ), + title: const Text('百度地图'), + subtitle: const Text('使用百度地图导航'), + onTap: () { + Get.back(); + openBaiduMap(destination, endlatitude, endlongitude, mode); + }, + ), + ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.map, color: Colors.red), + ), + title: const Text('谷歌地图'), + subtitle: const Text('使用谷歌地图导航'), + onTap: () { + Get.back(); + openGoogleMap(destination, mode); + }, + ), + // 苹果导航 - 仅在 iOS 平台显示 + if (Platform.isIOS) + ListTile( + leading: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.navigation, color: Colors.grey), + ), + title: const Text('苹果地图'), + subtitle: const Text('使用苹果地图导航'), + onTap: () { + Get.back(); + openAppleMap(destination, mode); + }, + ), + // 底部安全区域 + SizedBox(height: MediaQuery.of(Get.context!).padding.bottom + 20), + ], + ), + ), + isScrollControlled: true, + isDismissible: true, // 点击外部区域可关闭 + enableDrag: true, // 支持拖拽关闭 + ); + } + + // 使用默认配置导航 + void _navigateWithDefaultMode(String start, String destination, + String endlatitude, String endlongitude, String mode) { final navigationMode = AppConfig.env('APP_NAVIGATION_MODE') ?? 'external_auto'; if (navigationMode == 'internal_gaode') { _navigationManager.open(start, "$endlongitude,$endlatitude", mode); } else if (navigationMode == 'external_baidu') { - openBaiduMap(destination, mode); + openBaiduMap(destination, endlatitude, endlongitude, mode); } else if (navigationMode == 'external_gaode') { - openGaodeMap(destination, endlatitude, destination, mode); + openGaodeMap(destination, endlatitude, endlongitude, mode); } else if (navigationMode == 'external_google') { openGoogleMap(destination, mode); + } else { + // 自动选择导航应用,优先级:百度 > 高德 > 谷歌 + _autoSelectNavigation( + start, destination, endlatitude, endlongitude, mode); + } + } + + // 自动选择导航应用 + Future _autoSelectNavigation(String start, String destination, + String endlatitude, String endlongitude, String mode) async { + try { + // 1. 优先尝试百度地图 + final baiduUri = Uri( + scheme: 'baidumap', + host: 'map', + path: 'direction', + queryParameters: { + 'destination': destination, + 'coord_type': 'gcj02', + 'mode': mode == 'driving' ? 'driving' : 'transit', + 'src': 'deepvoice', + }, + ); + + if (await canLaunchUrl(baiduUri)) { + print('自动选择:使用百度地图'); + openBaiduMap(destination, endlatitude, endlongitude, mode); + return; + } + + // 2. 其次尝试高德地图 + final gaodeUri = Uri( + scheme: 'amapuri', + host: 'navi', + queryParameters: { + 'to': destination, + 'mode': mode.isNotEmpty ? mode : 'driving', + 'callnative': '1', + }, + ); + + if (await canLaunchUrl(gaodeUri)) { + print('自动选择:使用高德地图'); + openGaodeMap(destination, endlatitude, endlongitude, mode); + return; + } + + // 3. 然后尝试谷歌地图 + final googleUri = Uri( + scheme: 'comgooglemaps', + queryParameters: { + 'daddr': destination, + 'directionsmode': mode == 'driving' ? 'driving' : 'transit', + }, + ); + + if (await canLaunchUrl(googleUri)) { + print('自动选择:使用谷歌地图'); + openGoogleMap(destination, mode); + return; + } + + // 4. 在 iOS 平台尝试苹果地图 + if (Platform.isIOS) { + final appleUri = Uri( + scheme: 'maps', + queryParameters: { + 'daddr': destination, + 'dirflg': mode == 'driving' ? 'd' : 'r', + }, + ); + + if (await canLaunchUrl(appleUri)) { + print('自动选择:使用苹果地图'); + openAppleMap(destination, mode); + return; + } + } + + // 5. 如果都没有安装,提示用户 + print('未找到可用的导航应用'); + final availableApps = + Platform.isIOS ? '百度地图、高德地图、谷歌地图或苹果地图' : '百度地图、高德地图或谷歌地图'; + Get.snackbar('提示', '未找到可用的导航应用,请安装$availableApps'); + } catch (e) { + print('自动选择导航失败: $e'); + Get.snackbar('错误', '启动导航失败'); } } @@ -2187,7 +2403,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { } /// 打开Google地图进行导航 - Future openGoogleMap(String daddr, String mode) async { + Future openGoogleMap(String daddr, String mode) async { try { // 方案1: 尝试使用Google Maps应用的URL scheme // final Uri googleMapsUri = Uri.parse(url); @@ -2197,26 +2413,28 @@ class AgentController extends GetxController with WidgetsBindingObserver { 'comgooglemaps://?saddr=CurrentLocation&daddr=$daddr&directionsmode=$mode'); if (await canLaunchUrl(appUri)) { await launchUrl(appUri, mode: LaunchMode.externalApplication); - return; + return true; } else { final Uri webUrl = Uri.parse( "https://www.google.com/maps/dir/?api=1&origin=Current+Location&destination=$daddr&travelmode=$mode"); if (await canLaunchUrl(webUrl)) { await launchUrl(webUrl); + return true; } + return false; } } catch (e) { print('打开地图失败: $e'); - Get.snackbar('错误', '打开地图时发生错误'); + return false; } } /// 打开高德地图进行导航 - Future openGaodeMap( + Future openGaodeMap( String destination, String lat, String lon, - String mode, + String mode, // 'driving', 'transit', 'walking', 'cycling' ) async { try { // 获取应用包名 @@ -2224,92 +2442,174 @@ class AgentController extends GetxController with WidgetsBindingObserver { // 构建src参数(带平台前缀) final String src = Platform.isAndroid - ? "android.${packageInfo.packageName}" + ? packageInfo.packageName : "ios.${packageInfo.packageName}"; - // 构建高德地图导航链接 - final Uri uri = Uri( - scheme: 'androidamap', - host: 'navi', - queryParameters: { - 'poiname': destination, // 目的地地址 - 'lat': lat, - 'lon': lon, - 'mode': mode.isNotEmpty ? mode : 'driving', // 导航模式,默认驾车 - 'sourceApplication': src, // 来源标识 - 'callnative': '1', // 优先调用原生应用 - }, - ); + // 导航模式映射 + String gaodeMode; + switch (mode) { + case 'transit': + gaodeMode = '1'; // 公交 + break; + case 'walking': + gaodeMode = '2'; // 步行 + break; + case 'cycling': + gaodeMode = '3'; // 骑行 + break; + case 'driving': + default: + gaodeMode = '0'; // 驾车 + break; + } + + // 根据平台选择不同的 scheme 和参数 + final Uri uri; + if (Platform.isAndroid) { + uri = Uri( + scheme: 'androidamap', + host: 'route', + path: 'plan', + queryParameters: { + 'dlat': lat, + 'dlon': lon, + 'dname': destination, + 'dev': '0', // 0: gcj02 + 't': gaodeMode, + 'sourceApplication': src, + }, + ); + } else { + // iOS + uri = Uri( + scheme: 'amapuri', + host: 'route', + path: 'plan', + queryParameters: { + 'dlat': lat, + 'dlon': lon, + 'dname': destination, + 'dev': '0', // 0: gcj02 + 't': gaodeMode, + 'sourceApplication': src, + }, + ); + } print('高德导航链接: ${uri.toString()}'); - // final Uri testMapsUri = Uri.parse( - // 'androidamap://navi?sourceApplication=android.com.saitong.voitrans&lat=22.615274&lon=114.035576&poiname=深圳北站&dev=0&mode=driving'); - // // 尝试打开高德地图应用 + if (await canLaunchUrl(uri)) { final bool launched = await launchUrl( uri, mode: LaunchMode.externalApplication, ); - if (!launched) { - print('高德地图已安装,但启动失败'); - Get.snackbar('提示', '高德地图启动失败'); - } + return launched; } else { print('高德地图未安装'); Get.snackbar('提示', '请先安装高德地图应用'); + return false; } } catch (e) { - print('打开地图失败: $e'); + print('打开高德地图失败: $e'); Get.snackbar('错误', '打开地图时发生错误'); + return false; } } /// 打开Baidu地图进行导航 - Future openBaiduMap( + Future openBaiduMap( String destination, - String mode, + String lat, + String lon, + String mode, // 'driving', 'transit', 'walking', 'riding' ) async { try { - // 地址和城市(已编码的字符串也可以直接使用) - // String destination = "深圳北站"; - // String city = "深圳"; + // 获取应用包名 + final packageInfo = await PackageInfo.fromPlatform(); + final String src = + '${Platform.isAndroid ? '' : 'ios.'}${packageInfo.packageName}'; + + // 百度地图导航模式映射 + String baiduMode; + switch (mode) { + case 'transit': + baiduMode = 'transit'; // 公交 + break; + case 'walking': + baiduMode = 'walking'; // 步行 + break; + case 'cycling': + baiduMode = 'riding'; // 骑行 + break; + case 'driving': + default: + baiduMode = 'driving'; // 驾车 + break; + } - // 构建正确的导航链接 - final Uri appUri = Uri( + final Uri uri = Uri( scheme: 'baidumap', host: 'map', - path: 'direction', // 使用direction端点而非navi + path: 'direction', queryParameters: { - 'destination': destination, - // 'city': '深圳', - 'mode': mode, // 明确导航模式 - 'src': 'com.saitong.voitrans', // 带平台前缀的src参数 + 'destination': 'name:$destination|latlng:$lat,$lon', + 'coord_type': 'gcj02', + 'mode': baiduMode, + 'src': src, }, ); - print('生成的链接: ${appUri.toString()}'); + print('百度导航链接: ${uri.toString()}'); - if (await canLaunchUrl(appUri)) { - await launchUrl(appUri, mode: LaunchMode.externalApplication); + if (await canLaunchUrl(uri)) { + final bool launched = await launchUrl( + uri, + mode: LaunchMode.externalApplication, + ); + return launched; } else { - print('无法打开百度地图,请检查是否已安装'); + print('百度地图未安装'); + Get.snackbar('提示', '请先安装百度地图应用'); + return false; } - - // final packageInfo = await PackageInfo.fromPlatform(); - // String packageName = packageInfo.packageName; - // // 方案1: 尝试使用Google Maps应用的URL scheme - // // final Uri googleMapsUri = Uri.parse(url); - // // 简化版链接(仅包含必要参数) - // final Uri testUri = Uri.parse( - // 'baidumap://map/navi?location=$end_latitude,$end_longitude&coord_type=bd09ll&src$packageName'); - // if (await canLaunchUrl(testUri)) { - // await launchUrl(testUri, mode: LaunchMode.externalApplication); - // } else { - // print('打开地图失败'); - // } } catch (e) { - print('打开地图失败: $e'); + print('打开百度地图失败: $e'); Get.snackbar('错误', '打开地图时发生错误'); + return false; + } + } + + /// 打开苹果地图进行导航 + Future openAppleMap(String destination, String mode) async { + try { + // 构建苹果地图导航链接 + final Uri uri = Uri( + scheme: 'maps', + queryParameters: { + 'daddr': destination, // 目的地地址 + 'dirflg': mode == 'driving' ? 'd' : 'r', // d=驾车, r=公交 + }, + ); + + print('苹果地图导航链接: ${uri.toString()}'); + + // 尝试打开苹果地图应用 + if (await canLaunchUrl(uri)) { + final bool launched = await launchUrl( + uri, + mode: LaunchMode.externalApplication, + ); + if (!launched) { + print('苹果地图启动失败'); + Get.snackbar('提示', '苹果地图启动失败'); + } + } else { + print('苹果地图不可用'); + Get.snackbar('提示', '苹果地图不可用'); + } + } catch (e) { + print('打开苹果地图失败: $e'); + Get.snackbar('错误', '打开苹果地图时发生错误'); } } } diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index edb277bd9..31b900a5c 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -416,6 +416,7 @@ Widget navigationCard( borderRadius: BorderRadius.circular(12.r), onTap: () { controller.openNavigation( + true, start, destination, endlatitude, diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 0b0b2d8a1..99e0ed8e5 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -894,7 +894,7 @@ object AgentService : CoroutineScope { var userMetadata = JSONObject() var reply = true; //是否回复 var broadcast = true //是否播发 - var nocard = false; + var iscard = false; // 如果有图片,添加图片信息到元数据 if (hasImage) { @@ -1084,8 +1084,8 @@ object AgentService : CoroutineScope { aiMetadata = resultmap["meta"] as String; broadcast = resultmap["broadcast"] as Boolean; reply = !(resultmap["notreply"] as Boolean); - nocard = resultmap["nocard"] as Boolean; - if (!nocard){ + iscard = resultmap["iscard"] as Boolean; + if (iscard){ sendEvent( "function_call_result", mapOf( "function_call" to functionCall.toString(), @@ -1869,7 +1869,7 @@ object AgentService : CoroutineScope { val metadata: MutableMap = mutableMapOf() var broadcast = true; var notreply = false; - var nocard = false; + var iscard = false; if (textStr.isNotEmpty()) { val meta = JSONObject(textStr) if (meta.has("broadcast")) { @@ -1878,8 +1878,8 @@ object AgentService : CoroutineScope { if (meta.has("notreply")) { notreply = meta["notreply"] as Boolean } - if (meta.has("nocard")) { - nocard = meta["nocard"] as Boolean + if (meta.has("iscard")) { + iscard = meta["iscard"] as Boolean } metadata[toolname] = meta // 现在可以赋值 @@ -1970,13 +1970,13 @@ object AgentService : CoroutineScope { val end = cardNavigation.optString("end", "") processNavigation(start, end); } - return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"nocard" to nocard); + return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"iscard" to iscard); } } catch (e: Exception) { // 捕获其他类型的异常(可选) println("为解析到卡片数据!") } - return mapOf("meta" to "", "broadcast" to true, "notreply" to false,"nocard" to false); + return mapOf("meta" to "", "broadcast" to true, "notreply" to false,"iscard" to false); } /** diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt index e03ede703..cc40debdd 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MusicSystemFunctionHandler.kt @@ -147,7 +147,7 @@ private class MusicSearchHandler(private val context: Context?) : FunctionHandle // val ts = songMap["ts"] ?: 0 "{\"id\": $id, \"name\": \"$name\", \"singer\": \"$singer\",\"image\": \"$image\"}" } - "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" + "{\"success\": true, \"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" } else { val error = searchResult.exceptionOrNull() "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt index e8e70f9a7..db3684402 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt @@ -148,7 +148,7 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand val duration = songMap["duration"] ?: 0 "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" } - "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" + "{\"success\": true,\"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" } else { val error = searchResult.exceptionOrNull() "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"