From 13f9172cc9711ea5a86bf47f9fa0028680c00b48 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 16 Sep 2025 11:27:35 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=99=BE=E5=BA=A6?= =?UTF-8?q?=E5=A4=A9=E6=B0=94=E5=8D=A1=E7=89=87=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/message_bubble.dart | 21 + lib/modules/agent/views/weather_baidu.dart | 562 ++++++++++++++++++++ 2 files changed, 583 insertions(+) create mode 100644 lib/modules/agent/views/weather_baidu.dart diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index eda6c114f..d26670c65 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -2,6 +2,7 @@ import 'package:voitrans/core/utils/logger.dart'; import 'package:voitrans/modules/agent/views/google_navigation_card.dart'; import 'package:voitrans/modules/agent/views/music_card.dart'; import 'package:voitrans/modules/agent/views/qqmusic_card.dart'; +import 'package:voitrans/modules/agent/views/weather_baidu.dart'; import '../../../data/models/song_model.dart'; import '../../../modules/agent/views/navigation_card.dart'; @@ -258,6 +259,7 @@ class MessageBubble extends GetView { return buildQQMusicContent(context, songList); } } else if (meta!.keys.contains('maps_weather')) { + //高德的 final maps_weather = meta!['maps_weather'] as Map; final city = maps_weather['city']; if (city != null) { @@ -273,6 +275,25 @@ class MessageBubble extends GetView { daylist, ); } + } else if (meta!.keys.contains('map_weather')) { + //百度的 + final map_weather = meta!['map_weather'] as Map; + final status = map_weather['status']; + if (status == 0) { + final result = map_weather['result'] as Map; + final city = result["location"]["city"] as String? ?? ''; + final rawList = result['forecasts'] as List? ?? []; + final daylist = + rawList.map((e) => Map.from(e)).toList(); + final now = daylist.isNotEmpty ? daylist[0] : {}; + //天气 + return buildWeatherBaiduContent( + context, + city, + now, + daylist, + ); + } } else if (meta!.keys.contains('card_travel')) { final rawList = meta!['card_travel']['items'] as List? ?? []; final travellist = diff --git a/lib/modules/agent/views/weather_baidu.dart b/lib/modules/agent/views/weather_baidu.dart new file mode 100644 index 000000000..585eae209 --- /dev/null +++ b/lib/modules/agent/views/weather_baidu.dart @@ -0,0 +1,562 @@ +import 'package:common_utils/common_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +/// 构建天气内容卡片 +Widget buildWeatherBaiduContent(BuildContext context, String city, + Map now, List> days) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'weatherQuery'.tr, // 翻译键代表的中文文本内容:查询天气情况: + style: TextStyle( + color: + isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87, + fontSize: 14.sp, + height: 1.4, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 12.h), + Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + gradient: _getWeatherGradient(now['text_day'] ?? '', isDarkMode), + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${city} ${now["date"]}", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + SizedBox(height: 4.w), + Text( + _getWeatherDescriptionTranslation(now["text_day"]), + style: TextStyle( + fontSize: 14.sp, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + SizedBox(height: 8.h), + Row( + children: [ + Text( + "${now["high"]}°", + style: TextStyle( + fontSize: 36.sp, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${'highest'.tr}: ${now["high"]}°', // 翻译键代表的中文文本内容:最高 + style: TextStyle( + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + Text( + '${'lowest'.tr}: ${now["low"]}°', // 翻译键代表的中文文本内容:最低 + style: TextStyle( + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + ], + ), + ], + ), + ], + ), + Icon( + getWeatherIcon(now['text_day']), + size: 48.sp, + color: Colors.white, + ), + ], + ), + SizedBox(height: 16.h), + Container( + height: 1, + color: Colors.white.withValues(alpha: isDarkMode ? 0.15 : 0.2), + ), + SizedBox(height: 16.h), + Text( + 'futureForecast'.tr, // 翻译键代表的中文文本内容:未来预报 + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate( + days.length, + (index) { + String dateStr = days[index]['date']; + DateTime? date = DateUtil.getDateTime(dateStr); + IconData weatherIcon = + getWeatherIcon(days[index]['text_day']); + + // 获取星期几的翻译键 + String weekdayKey = _getWeekdayTranslationKey(date); + String weekday = weekdayKey.tr; // 翻译键代表的中文文本内容:对应的星期几 + + String temp = "${days[index]['high']}°"; + return _buildForecastDay( + weekday, + weatherIcon, + temp, + ); + }, + ), + ), + SizedBox(height: 16.h), + _buildWeatherTip(context, now, isDarkMode), + ], + ), + ), + ], + ); +} + +/// 获取天气描述的翻译 +String _getWeatherDescriptionTranslation(String weatherDescription) { + final description = weatherDescription.trim(); + + // 天气描述映射表 + final Map weatherMap = { + // 晴天类 + '晴': 'sunny'.tr, // 翻译键代表的中文文本内容:晴 + '少云': 'partlyCloudy'.tr, // 翻译键代表的中文文本内容:少云 + '晴间多云': 'partlySunny'.tr, // 翻译键代表的中文文本内容:晴间多云 + + // 多云类 + '多云': 'cloudy'.tr, // 翻译键代表的中文文本内容:多云 + '阴': 'overcast'.tr, // 翻译键代表的中文文本内容:阴 + + // 雨天类 + '阵雨': 'shower'.tr, // 翻译键代表的中文文本内容:阵雨 + '雷阵雨': 'thundershower'.tr, // 翻译键代表的中文文本内容:雷阵雨 + '小雨': 'lightRain'.tr, // 翻译键代表的中文文本内容:小雨 + '中雨': 'moderateRain'.tr, // 翻译键代表的中文文本内容:中雨 + '大雨': 'heavyRain'.tr, // 翻译键代表的中文文本内容:大雨 + '暴雨': 'rainstorm'.tr, // 翻译键代表的中文文本内容:暴雨 + + // 雪天类 + '阵雪': 'snowShower'.tr, // 翻译键代表的中文文本内容:阵雪 + '小雪': 'lightSnow'.tr, // 翻译键代表的中文文本内容:小雪 + '中雪': 'moderateSnow'.tr, // 翻译键代表的中文文本内容:中雪 + '大雪': 'heavySnow'.tr, // 翻译键代表的中文文本内容:大雪 + '暴雪': 'blizzard'.tr, // 翻译键代表的中文文本内容:暴雪 + + // 雾霾类 + '雾': 'fog'.tr, // 翻译键代表的中文文本内容:雾 + '霾': 'haze'.tr, // 翻译键代表的中文文本内容:霾 + + // 风类 + '大风': 'strongWind'.tr, // 翻译键代表的中文文本内容:大风 + }; + + // 返回翻译后的描述,如果没有找到对应翻译则返回原文 + return weatherMap[description] ?? description; +} + +/// 获取星期几的翻译键 +String _getWeekdayTranslationKey(DateTime? date) { + if (date == null) return 'unknown'; + + switch (date.weekday) { + case 1: + return 'monday'; // 翻译键代表的中文文本内容:周一 + case 2: + return 'tuesday'; // 翻译键代表的中文文本内容:周二 + case 3: + return 'wednesday'; // 翻译键代表的中文文本内容:周三 + case 4: + return 'thursday'; // 翻译键代表的中文文本内容:周四 + case 5: + return 'friday'; // 翻译键代表的中文文本内容:周五 + case 6: + return 'saturday'; // 翻译键代表的中文文本内容:周六 + case 7: + return 'sunday'; // 翻译键代表的中文文本内容:周日 + default: + return 'unknown'; + } +} + +/// 构建智能天气提示 +Widget _buildWeatherTip( + BuildContext context, Map now, bool isDarkMode) { + final weatherTip = _generateWeatherTip(now); + + return Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 12.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withValues(alpha: 0.15) + : Colors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + children: [ + Icon( + weatherTip.icon, + size: 16.sp, + color: Colors.white, + ), + SizedBox(width: 8.w), + Flexible( + child: Text( + weatherTip.message, + style: TextStyle( + fontSize: 12.sp, + color: Colors.white, + ), + ), + ), + ], + ), + ); +} + +/// 天气提示数据类 +class WeatherTip { + final IconData icon; + final String message; + + const WeatherTip({required this.icon, required this.message}); +} + +/// 根据天气数据生成智能提示 +WeatherTip _generateWeatherTip(Map now) { + final dayWeather = (now['dayweather'] as String? ?? '').trim(); + final dayTemp = int.tryParse(now['daytemp']?.toString() ?? '0') ?? 0; + final nightTemp = int.tryParse(now['nighttemp']?.toString() ?? '0') ?? 0; + + // 高温提示 (>= 35°C) + if (dayTemp >= 35) { + return WeatherTip( + icon: Icons.local_fire_department, + message: 'hotWeatherTip'.tr, // 翻译键代表的中文文本内容:今日高温炎热,请注意防暑降温,多喝水避免中暑 + ); + } + + // 低温提示 (<= 5°C) + if (dayTemp <= 5) { + return WeatherTip( + icon: Icons.ac_unit, + message: 'coldWeatherTip'.tr, // 翻译键代表的中文文本内容:今日气温较低,外出请注意保暖,小心感冒 + ); + } + + // 雨天提示 + if (dayWeather.contains('雨') || + ['阵雨', '雷阵雨', '小雨', '中雨', '大雨', '暴雨'] + .any((rain) => dayWeather.contains(rain))) { + return WeatherTip( + icon: Icons.umbrella, + message: 'rainyWeatherTip'.tr, // 翻译键代表的中文文本内容:今日有雨,外出请携带雨具,注意出行安全 + ); + } + + // 雪天提示 + if (dayWeather.contains('雪') || + ['阵雪', '小雪', '中雪', '大雪', '暴雪'].any((snow) => dayWeather.contains(snow))) { + return WeatherTip( + icon: Icons.ac_unit, + message: 'snowyWeatherTip'.tr, // 翻译键代表的中文文本内容:今日有雪,路面湿滑请注意安全,做好保暖措施 + ); + } + + // 雾霾提示 + if (dayWeather.contains('雾') || + dayWeather.contains('霾') || + ['雾', '浓雾', '霾', '中度霾', '重度霾'].any((fog) => dayWeather.contains(fog))) { + return WeatherTip( + icon: Icons.blur_on, + message: 'foggyWeatherTip'.tr, // 翻译键代表的中文文本内容:今日有雾霾,能见度较低,外出请佩戴口罩 + ); + } + + // 大风提示 + if (dayWeather.contains('风') || + ['大风', '强风', '疾风', '烈风'].any((wind) => dayWeather.contains(wind))) { + return WeatherTip( + icon: Icons.air, + message: 'windyWeatherTip'.tr, // 翻译键代表的中文文本内容:今日风力较大,外出请注意安全,固定好物品 + ); + } + + // 晴天紫外线提示 (温度 >= 25°C) + if (['晴', '少云', '晴间多云'].any((sunny) => dayWeather.contains(sunny)) && + dayTemp >= 25) { + return WeatherTip( + icon: Icons.wb_sunny, + message: 'sunnyWeatherTip'.tr, // 翻译键代表的中文文本内容:今日阳光充足紫外线较强,外出请做好防晒 + ); + } + + // 温差大提示 (>= 10°C) + if ((dayTemp - nightTemp).abs() >= 10) { + return WeatherTip( + icon: Icons.thermostat, + message: 'temperatureDifferenceTip'.tr, // 翻译键代表的中文文本内容:今日昼夜温差较大,请适时增减衣物 + ); + } + + // 默认舒适天气提示 + return WeatherTip( + icon: Icons.wb_sunny_outlined, + message: 'comfortableWeatherTip'.tr, // 翻译键代表的中文文本内容:今日天气适宜,是外出活动的好时光 + ); +} + +/// 构建单日天气预报 +Widget _buildForecastDay(String day, IconData icon, String temp) { + return Column( + children: [ + Text( + day, + style: TextStyle( + fontSize: 12.sp, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + SizedBox(height: 4.h), + Icon( + icon, + size: 20.sp, + color: Colors.white, + ), + SizedBox(height: 4.h), + Text( + temp, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ], + ); +} + +/// 获取天气图标 +IconData getWeatherIcon(String description) { + description = description.trim(); + + if (["晴", "少云", "晴间多云"].contains(description)) { + return Icons.wb_sunny; + } + + if (["多云", "阴"].contains(description)) { + return Icons.cloud; + } + + if (description.contains("雾") || + ["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"] + .contains(description)) { + return Icons.blur_on; + } + + if (description.contains("风") || + [ + "有风", + "平静", + "微风", + "和风", + "清风", + "强风/劲风", + "疾风", + "大风", + "烈风", + "风暴", + "狂爆风", + "飓风", + "热带风暴" + ].contains(description)) { + return Icons.air; + } + + if (description.contains("雨") || + [ + "阵雨", + "雷阵雨", + "雷阵雨并伴有冰雹", + "小雨", + "中雨", + "大雨", + "暴雨", + "大暴雨", + "特大暴雨", + "强阵雨", + "强雷阵雨", + "极端降雨", + "毛毛雨/细雨", + "雨", + "小雨-中雨", + "中雨-大雨", + "大雨-暴雨", + "暴雨-大暴雨", + "大暴雨-特大暴雨" + ].contains(description)) { + return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain + } + + if (description.contains("雪") || + [ + "雨雪天气", + "雨夹雪", + "阵雨夹雪", + "冻雨", + "雪", + "阵雪", + "小雪", + "中雪", + "大雪", + "暴雪", + "小雪-中雪", + "中雪-大雪", + "大雪-暴雪" + ].contains(description)) { + return Icons.ac_unit; + } + + if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) { + return Icons.filter_drama; + } + + if (["龙卷风"].contains(description)) { + return Icons.waves; + } + + if (["热"].contains(description)) { + return Icons.local_fire_department; + } + + if (["冷"].contains(description)) { + return Icons.ac_unit; + } + + // 默认或未知天气 + return Icons.help_outline; +} + +/// 根据天气类型获取背景渐变颜色 +LinearGradient _getWeatherGradient(String weather, bool isDarkMode) { + final weatherType = weather.trim(); + + // 晴天 - 温暖的橙黄色 + if (['晴', '少云', '晴间多云'].any((sunny) => weatherType.contains(sunny))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFFFF8A00), Color(0xFFE65100)] // 深色模式:深橙色 + : [Color(0xFFFFB74D), Color(0xFFFF9800)], // 浅色模式:亮橙色 + ); + } + + // 多云/阴天 - 灰蓝色 + if (['多云', '阴'].any((cloudy) => weatherType.contains(cloudy))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF455A64), Color(0xFF37474F)] // 深色模式:深灰蓝 + : [Color(0xFF78909C), Color(0xFF607D8B)], // 浅色模式:灰蓝色 + ); + } + + // 雨天 - 深蓝色 + if (weatherType.contains('雨') || + ['阵雨', '雷阵雨', '小雨', '中雨', '大雨', '暴雨'] + .any((rain) => weatherType.contains(rain))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF1565C0), Color(0xFF0D47A1)] // 深色模式:深蓝色 + : [Color(0xFF42A5F5), Color(0xFF1976D2)], // 浅色模式:亮蓝色 + ); + } + + // 雪天 - 冷色调蓝白色 + if (weatherType.contains('雪') || + ['阵雪', '小雪', '中雪', '大雪', '暴雪'] + .any((snow) => weatherType.contains(snow))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF0277BD), Color(0xFF01579B)] // 深色模式:深冷蓝 + : [Color(0xFF81D4FA), Color(0xFF29B6F6)], // 浅色模式:浅蓝色 + ); + } + + // 雾霾天 - 暗黄灰色 + if (weatherType.contains('雾') || + weatherType.contains('霾') || + ['雾', '浓雾', '霾', '中度霾', '重度霾'].any((fog) => weatherType.contains(fog))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF5D4037), Color(0xFF3E2723)] // 深色模式:深棕色 + : [Color(0xFF8D6E63), Color(0xFF6D4C41)], // 浅色模式:棕灰色 + ); + } + + // 大风天 - 青绿色 + if (weatherType.contains('风') || + ['大风', '强风', '疾风', '烈风'].any((wind) => weatherType.contains(wind))) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF00695C), Color(0xFF004D40)] // 深色模式:深青色 + : [Color(0xFF26A69A), Color(0xFF00796B)], // 浅色模式:青绿色 + ); + } + + // 默认 - 原来的蓝色 + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isDarkMode + ? [Color(0xFF1E40AF), Color(0xFF1E3A8A)] + : [Color(0xFF60A5FA), Color(0xFF3B82F6)], + ); +} From b0c2448723511a3de98bdefa6a51a4ac308f16fe Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 18 Sep 2025 10:41:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 10 ++ lib/data/services/ble_manager.dart | 3 + .../agent/controllers/agent_controller.dart | 133 +++++++++++++++++- lib/modules/agent/views/message_bubble.dart | 4 + lib/modules/agent/views/navigation_card.dart | 9 +- .../agent_service/AgentService.kt | 26 ++-- .../agent_service/AgentServicePlugin.kt | 1 + .../agent_service/BleMediaService.kt | 5 +- 8 files changed, 171 insertions(+), 20 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3d1ae458c..e1ca0f551 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -132,4 +132,14 @@ + + + + + + + + + + diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index e208e74bc..dbf18918d 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -657,6 +657,8 @@ class BleManager extends GetxService { AppConfig.env('OPENAI_AGENT_ID') ?? 'personal_assistant'; final systemPrompt = AppConfig.agentsystemPrompt(openaiagentid); final musicServiceType = AppConfig.env('APP_MUSICSERVICE_TYPE') ?? 'AUTO'; + final navigationMode = + AppConfig.env('APP_NAVIGATION_MODE') ?? 'external_auto'; // 从Flutter资源包中加载.mcp.json final jsonData = AppConfig.mcpConfig(); Logger.info('启动BLE服务,musicServiceType: ${musicServiceType}'); @@ -728,6 +730,7 @@ class BleManager extends GetxService { 'usertoken': jsonString, 'supportedLanguages': supportedLanguages, 'musicServiceType': musicServiceType, + "navigationMode": navigationMode }; // 记录日志(不记录敏感信息) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 78fa49473..3ad7a9657 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -4,7 +4,9 @@ import 'dart:ffi'; import 'dart:io'; import 'package:amap_service/amap_service.dart'; import 'package:http/http.dart' as http; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:voitrans/data/models/appconfig.dart'; import 'package:voitrans/data/models/song_model.dart'; import 'package:voitrans/data/services/ble_manager.dart'; import 'package:voitrans/data/services/bluetooth_manager.dart'; @@ -172,7 +174,6 @@ class AgentController extends GetxController with WidgetsBindingObserver { _setupTextInputListener(); //设置文本输入监听器 startVoiceInput(); //开启语音输入 - // 监听应用生命周期:当应用切到后台/失焦时兜底结束按住说话,防止 onPointerUp 未触发 WidgetsBinding.instance.addObserver(this); @@ -812,13 +813,22 @@ class AgentController extends GetxController with WidgetsBindingObserver { if (card_navigation is Map) { //导航 final start = card_navigation["start"] as String; - final end = card_navigation["end"] as String; + final destination = + card_navigation["destination"] as String; + final end_longitude = + card_navigation["end_longitude"] as String; + final end_latitude = + card_navigation["end_latitude"] as String; + // End_longitude string `json:"end_longitude"` //目标点经度 + // End_latitude string `json:"end_latitude"` //目标点纬度 final items = card_navigation['items']; if (items != null) { Future.delayed(Duration(seconds: 2), () { // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); // 2秒后执行的代码 - openNavigation(start, end, ""); + + openNavigation(start, destination, end_latitude, + end_longitude, ""); }); } } @@ -1855,8 +1865,19 @@ class AgentController extends GetxController with WidgetsBindingObserver { } //去导航 - void openNavigation(String start, String end, String ntype) { - _navigationManager.open(start, end, ntype); + void openNavigation(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); + } else if (navigationMode == 'external_gaode') { + openGaodeMap(destination, endlatitude, destination, mode); + } else if (navigationMode == 'external_google') { + openGoogleMap(destination, mode); + } } // 打开图片查看器 @@ -2189,4 +2210,106 @@ class AgentController extends GetxController with WidgetsBindingObserver { Get.snackbar('错误', '打开地图时发生错误'); } } + + /// 打开高德地图进行导航 + Future openGaodeMap( + String destination, + String lat, + String lon, + String mode, + ) async { + try { + // 获取应用包名 + final packageInfo = await PackageInfo.fromPlatform(); + + // 构建src参数(带平台前缀) + final String src = Platform.isAndroid + ? "android.${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', // 优先调用原生应用 + }, + ); + + 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('提示', '高德地图启动失败'); + } + } else { + print('高德地图未安装'); + Get.snackbar('提示', '请先安装高德地图应用'); + } + } catch (e) { + print('打开地图失败: $e'); + Get.snackbar('错误', '打开地图时发生错误'); + } + } + + /// 打开Baidu地图进行导航 + Future openBaiduMap( + String destination, + String mode, + ) async { + try { + // 地址和城市(已编码的字符串也可以直接使用) + // String destination = "深圳北站"; + // String city = "深圳"; + + // 构建正确的导航链接 + final Uri appUri = Uri( + scheme: 'baidumap', + host: 'map', + path: 'direction', // 使用direction端点而非navi + queryParameters: { + 'destination': destination, + // 'city': '深圳', + 'mode': mode, // 明确导航模式 + 'src': 'com.saitong.voitrans', // 带平台前缀的src参数 + }, + ); + + print('生成的链接: ${appUri.toString()}'); + + if (await canLaunchUrl(appUri)) { + await launchUrl(appUri, mode: LaunchMode.externalApplication); + } else { + print('无法打开百度地图,请检查是否已安装'); + } + + // 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'); + Get.snackbar('错误', '打开地图时发生错误'); + } + } } diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index d26670c65..63615d5e0 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -307,6 +307,8 @@ class MessageBubble extends GetView { maps_route_navigation["card_navigation"] as Map; final start = card_navigation['start'] as String? ?? ''; final end = card_navigation['end'] as String? ?? ''; + final end_longitude = card_navigation["end_longitude"] as String; + final end_latitude = card_navigation["end_latitude"] as String; final destination = card_navigation['destination'] as String? ?? ''; final rawList = card_navigation['items'] as List? ?? null; if (rawList != null) { @@ -320,6 +322,8 @@ class MessageBubble extends GetView { return navigationCard(context, start: start, end: end, + endlatitude: end_latitude, + endlongitude: end_longitude, destination: destination, modes: list, navigation: navigation); diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index b5d904fff..edb277bd9 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -45,6 +45,8 @@ Widget navigationCard( BuildContext context, { required String start, required String end, + required String endlatitude, + required String endlongitude, required String destination, required List> modes, Map? navigation, @@ -413,7 +415,12 @@ Widget navigationCard( child: InkWell( borderRadius: BorderRadius.circular(12.r), onTap: () { - controller.openNavigation(start, end, ""); + controller.openNavigation( + start, + destination, + endlatitude, + endlongitude, + modes[currentIndex]["ntype"] as String); }, child: Container( padding: EdgeInsets.symmetric( 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 5258483c9..0b0b2d8a1 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 @@ -138,7 +138,7 @@ object AgentService : CoroutineScope { private val INTERRUPT_DEBOUNCE_MS = 300L var audioSourceType = AzureAsrHelper.AudioSourceType.MICROPHONE - + var navigationMode = "" // 语气词过滤列表(中英文) private val FILLER_WORDS = setOf( // 中文语气词 @@ -238,7 +238,7 @@ object AgentService : CoroutineScope { // 初始化TTS服务 initializeTtsEngine(config) - + navigationMode = config["navigationMode"]?.toString() ?: "" // 初始化ChatAPI服务 chatApiService.initialize( config["openaiApiKey"]?.toString() ?: "", @@ -2058,16 +2058,18 @@ object AgentService : CoroutineScope { * @return 是否成功开始处理 */ fun processNavigation(start: String, end: String) { - // 如果 playlist 中有有效的歌曲,开始播放 - if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { - NavigationServiceHelper.startNavigation( - context, - "start", - startpos = start, - endpos = end - ) - } else { - Log.i(TAG, "启动导航服务失败") + if (navigationMode == "internal_gaode"){ + // 如果 playlist 中有有效的歌曲,开始播放 + if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { + NavigationServiceHelper.startNavigation( + context, + "start", + startpos = start, + endpos = end + ) + } else { + Log.i(TAG, "启动导航服务失败") + } } } diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt index 776a926e7..e36f91294 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt @@ -76,6 +76,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream config["serverurl"]?.toString()?.let { intent.putExtra("serverurl", it) } config["usertoken"]?.toString()?.let { intent.putExtra("usertoken", it) } config["musicServiceType"]?.toString()?.let { intent.putExtra("musicServiceType", it) } + config["navigationMode"]?.toString()?.let { intent.putExtra("navigationMode", it) } // 添加 supportedLanguages 参数处理 config["supportedLanguages"]?.let { languages -> diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt index 24cdd92d6..97d2e9a1d 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleMediaService.kt @@ -53,7 +53,7 @@ class BleMediaService : Service(), CoroutineScope { private const val KEY_USER_TOKEN = "usertoken" private const val KEY_SUPPORTED_LANGUAGES = "supportedLanguages" private const val KEY_MUSICSERVICE_TYPE = "musicServiceType" - + private const val APP_NAVIGATION_MODE = "navigationMode" // 用于跟踪服务是否正在运行 private val isRunning = AtomicBoolean(false) @@ -182,7 +182,7 @@ class BleMediaService : Service(), CoroutineScope { it.getStringExtra(KEY_SERVER_URL)?.let { value -> config[KEY_SERVER_URL] = value } it.getStringExtra(KEY_USER_TOKEN)?.let { value -> config[KEY_USER_TOKEN] = value } it.getStringExtra(KEY_MUSICSERVICE_TYPE)?.let { value -> config[KEY_MUSICSERVICE_TYPE] = value } - + it.getStringExtra(APP_NAVIGATION_MODE)?.let { value -> config[APP_NAVIGATION_MODE] = value } // 提取 supportedLanguages 参数 it.getStringArrayListExtra(KEY_SUPPORTED_LANGUAGES)?.let { languages -> if (languages.isNotEmpty()) { @@ -242,6 +242,7 @@ class BleMediaService : Service(), CoroutineScope { config[KEY_SERVER_URL] = getConfigString(prefs, KEY_SERVER_URL, "") config[KEY_USER_TOKEN] = getConfigString(prefs, KEY_USER_TOKEN, "") config[KEY_MUSICSERVICE_TYPE] = getConfigString(prefs, KEY_MUSICSERVICE_TYPE, "") + config[APP_NAVIGATION_MODE] = getConfigString(prefs, APP_NAVIGATION_MODE, "") // 添加 supportedLanguages 参数处理 val supportedLanguagesStr = getConfigString(prefs, KEY_SUPPORTED_LANGUAGES, "")