diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 7f5b428bd..3911111a9 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -1,4 +1,5 @@ import 'package:deep_voice/data/services/music_manager.dart'; +import 'package:deep_voice/data/services/navigation_manager.dart'; import 'package:get/get.dart'; import '../../data/services/speech_factory.dart'; import '../../data/services/chat_history_service.dart'; @@ -35,6 +36,8 @@ class InitialBinding extends Bindings { Get.lazyPut(() => BleManager(), fenix: true); // MusicManager Get.lazyPut(() => MusicManager(), fenix: true); + // NavigationManager + Get.lazyPut(() => NavigationManager(), fenix: true); // 火山AI服务 // Get.lazyPut(() => VolcanoAIService(), fenix: true); diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 5a7b3b7d5..a67bac4f3 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -332,6 +332,12 @@ const Map zhCN = { "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?", "personalAssistantVoiceWelcome": "我在呢!", + // 导航 + "driving": "驾车", + "walking": "步行", + "bicycling": "骑行", + "tsransit": "公交", + // 同声传译 Lucy "interpreterName": "同声传译", "interpreterDescription": diff --git a/lib/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index f7b2ddbd0..5604838c7 100644 --- a/lib/data/services/navigation_manager.dart +++ b/lib/data/services/navigation_manager.dart @@ -1,4 +1,4 @@ -// import 'package:amap_service/amap_service.dart'; +import 'package:amap_service/amap_service.dart'; import 'package:deep_voice/core/utils/logger.dart'; import 'package:get/get.dart'; @@ -7,7 +7,7 @@ class NavigationManager extends GetxService { static NavigationManager get to => Get.find(); // 音乐服务实例 - // final AmapService _amapervice = AmapService(); + final AmapService _amapervice = AmapService(); /// 初始化 @override @@ -22,8 +22,20 @@ class NavigationManager extends GetxService { super.onClose(); } + Stream events() { + return _amapervice.events; + } + //导航 - Future navigation(String org, String dis) async { + Future start(String org, String dis) async { // await _amapervice.navigation(org, dis); } + //导航 + Future open( + String start, + String end, + String ntype, + ) async { + await _amapervice.open(start, end, ntype); + } } diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 446845002..990daad8c 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:amap_service/amap_service.dart'; import 'package:deep_voice/data/services/music_manager.dart'; +import 'package:deep_voice/data/services/navigation_manager.dart'; +import 'package:deep_voice/routes/app_routes.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:agent_service/agent_service.dart'; @@ -46,6 +49,7 @@ class AgentController extends GetxController { //音乐播放管理 final _musiceManager = Get.find(); + final _navigationManager = Get.find(); var isPlaying = false.obs; var currentUrl = "".obs; @@ -73,6 +77,9 @@ class AgentController extends GetxController { // 流事件订阅 StreamSubscription? _eventSubscription; + // 导航流事件订阅 + StreamSubscription? _amapeventSubscription; + // 用于跟踪AI回复状态 bool _isNewAssistantResponse = true; String _currentResponseId = ''; @@ -419,7 +426,7 @@ class AgentController extends GetxController { if (meta != null && meta is String && meta.isNotEmpty) { try { - metaResult = jsonDecode(meta); + metaResult = jsonDecode(meta) as Map; } catch (e) { Logger.e(TAG, '解析meta数据失败: $e'); } @@ -444,6 +451,16 @@ class AgentController extends GetxController { ); } } + if (metaResult.containsKey("card_navigation")) { + final start = metaResult["card_navigation"]["start"] as String; + final end = metaResult["card_navigation"]["end"] as String; + // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); + Future.delayed(Duration(milliseconds: 1500), () { + // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); + // 2秒后执行的代码 + openNavigation(start, end, ""); + }); + } } } break; @@ -451,6 +468,58 @@ class AgentController extends GetxController { break; } }); + + //后台导航事件同步 + _amapeventSubscription = _navigationManager.events().listen((event) { + switch (event.type) { + case AmapServiceEventType.starte: + Logger.i(TAG, '导航通知 started!'); + break; + case AmapServiceEventType.stope: + Logger.i(TAG, '导航通知 stoped!'); + break; + case AmapServiceEventType.update: + Logger.i(TAG, '导航通知 update! $event'); + Message? result; + + try { + result = messages.lastWhere( + (msg) => + !msg.isUser && + msg.meta?.containsKey("card_navigation") == true, + ); + } catch (e) { + result = null; + } + + if (result != null) { + int index = messages.indexOf(result); + if (index != -1) { + // ✅ 创建 meta 的深拷贝 + final newMeta = Map.from(result.meta ?? {}); + newMeta["card_navigation"]["navigation"] = event.data; + + // ✅ 创建新的 Message 替换原来的 + messages[index] = Message( + isUser: result.isUser, + text: result.text, + meta: newMeta, + timestamp: result.timestamp, + ); + messages.refresh(); + Logger.i( + TAG, '更新导航卡片! ${newMeta["card_navigation"]["navigation"]}'); + } + } + break; + case AmapServiceEventType.error: + Logger.i(TAG, '导航通知 error!'); + break; + case AmapServiceEventType.unknown: + Logger.i(TAG, '导航通知 unknown!'); + break; + } + }); } // 发送消息(包括所有支持的消息类型) @@ -885,4 +954,9 @@ class AgentController extends GetxController { AgentService.processTextInput(messages[index].text, speakResponse: isTtsEnabled.value); } + + //去导航 + void openNavigation(String start, String end, String ntype) { + _navigationManager.open(start, end, ntype); + } } diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 38845bbfc..14964195b 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -1,5 +1,7 @@ +import 'dart:convert'; + import 'package:common_utils/common_utils.dart'; -import 'package:deep_voice/modules/agent/views/message_bubble_navigation.dart'; +import 'package:deep_voice/modules/agent/views/navigation_card.dart'; import 'weather_card.dart'; import 'music_card.dart'; import 'travel_card.dart'; @@ -290,10 +292,22 @@ class MessageBubble extends GetView { // 旅游 return buildTravelContent(context, travellist); } else if (meta!.keys.contains('card_navigation')) { + final start = meta!['card_navigation']['start'] as String; + final end = meta!['card_navigation']['end'] as String; + final destination = meta!['card_navigation']['destination'] as String; final rawList = meta!['card_navigation']['items'] as List; final list = rawList.map((e) => Map.from(e)).toList(); + final rawNavigation = meta!['card_navigation']['navigation']; + final Map? navigation = + rawNavigation is Map ? rawNavigation : null; + print("导航实时数据 ${rawNavigation}"); // 导航 - return navigationCard(context, modes: list); + return navigationCard(context, + start: start, + end: end, + destination: destination, + modes: list, + navigation: navigation); } // if (message.contains('播放列表') || message.contains('音乐')) { diff --git a/lib/modules/agent/views/message_bubble_navigation.dart b/lib/modules/agent/views/message_bubble_navigation.dart deleted file mode 100644 index 8ee1cd4bd..000000000 --- a/lib/modules/agent/views/message_bubble_navigation.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; - -/// A widget that displays a navigation card with different modes of transportation. -Widget navigationCard( - BuildContext context, { - required List> modes, - double height = 260, -}) { - int currentIndex = 0; - - return Container( - height: height, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: Colors.white, - boxShadow: const [ - BoxShadow( - blurRadius: 8, - color: Colors.black12, - offset: Offset(0, 4), - ), - ], - ), - child: StatefulBuilder( - builder: (context, setInnerState) { - return Column( - children: [ - // 路线图片 - ClipRRect( - borderRadius: - const BorderRadius.vertical(top: Radius.circular(16)), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: Image.network( - modes[currentIndex]["image"] as String, - key: ValueKey(modes[currentIndex]["ntype"] as String), - height: 160, - width: double.infinity, - fit: BoxFit.cover, - ), - ), - ), - - // 导航方式切换按钮 - Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate(modes.length, (index) { - final isSelected = index == currentIndex; - return GestureDetector( - onTap: () { - setInnerState(() => currentIndex = index); - }, - child: SizedBox( - width: 50, - child: Text( - modes[index]["ntype"] as String, - style: TextStyle( - color: isSelected ? Colors.blue : Colors.black87, - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - ), - ); - }), - ), - ), - - // 距离和耗时 - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('距离:${modes[currentIndex]["distance"]}', - style: const TextStyle(fontSize: 12, color: Colors.blue)), - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, // 👈 圆形背景 - color: Colors.blue, - ), - child: Icon(Icons.turn_left, - color: Colors.white, // 👈 圆形图标 - size: 20), - ) - ], - ), - ), - ], - ); - }, - ), - ); -} diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart new file mode 100644 index 000000000..0ad88dfe3 --- /dev/null +++ b/lib/modules/agent/views/navigation_card.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/agent_controller.dart'; + +String formatDistance(String distanceStr) { + final distance = double.tryParse(distanceStr); + if (distance == null) return distanceStr; // 如果无法解析,则返回原始字符串 + + if (distance >= 1000) { + final km = distance / 1000; + return '${km.toStringAsFixed(1)} 公里'; + } else { + return '${distance.toStringAsFixed(0)} 米'; + } +} + +String formatDuration(String secondsStr) { + int totalSeconds = int.tryParse(secondsStr) ?? 0; + Duration duration = Duration(seconds: totalSeconds); + + int hours = duration.inHours; + int minutes = duration.inMinutes.remainder(60); + int seconds = duration.inSeconds.remainder(60); + + List parts = []; + if (hours > 0) parts.add('$hours小时'); + if (minutes > 0) parts.add('$minutes分钟'); + if (seconds > 0 || parts.isEmpty) parts.add('$seconds秒'); + + return parts.join(''); +} + +//导航卡片 +Widget navigationCard( + BuildContext context, { + required String start, + required String end, + required String destination, + required List> modes, + Map? navigation, + double height = 300, +}) { + int currentIndex = 0; + final controller = Get.find(); // 获取控制器 + return Container( + height: height, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + boxShadow: const [ + BoxShadow( + blurRadius: 8, + color: Colors.black12, + offset: Offset(0, 4), + ), + ], + ), + child: StatefulBuilder( + builder: (context, setInnerState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 路线图片 + ClipRRect( + borderRadius: + const BorderRadius.vertical(top: Radius.circular(16)), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Image.network( + modes[currentIndex]["image"] as String, + key: ValueKey(modes[currentIndex]["ntype"] as String), + height: 160, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + ), + + // 导航方式切换按钮 + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(modes.length, (index) { + final isSelected = index == currentIndex; + return GestureDetector( + onTap: modes[index]['enable'] as bool + ? () { + setInnerState(() => currentIndex = index); + } + : null, + child: SizedBox( + width: 50, + child: Text( + (modes[index]["ntype"] as String).tr, + textAlign: TextAlign.center, + style: TextStyle( + color: modes[index]['enable'] + ? isSelected + ? Colors.blue + : Colors.black87 + : Colors.grey, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + }), + ), + ), + + // 距离和耗时 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('$destination', + style: const TextStyle( + fontSize: 12, color: Colors.black)), + Text( + '${formatDistance(modes[currentIndex]["distance"])}-约${formatDuration(modes[currentIndex]["duration"])}', + style: const TextStyle( + fontSize: 15, color: Colors.blue)), + if (navigation != null) + Text( + "导航中... ${navigation["distanceText"]}进入${navigation["nextRoad"]}", + style: TextStyle( + fontSize: 12, + color: + const Color.fromARGB(255, 164, 164, 164))) + ], + ), + CircleAvatar( + backgroundColor: Colors.blue, + radius: 18, + child: IconButton( + icon: const Icon(Icons.turn_left, + color: Colors.white, size: 20), + onPressed: () { + controller.openNavigation(start, end, ""); + }, + ), + ) + ], + ), + ), + ], + ); + }, + ), + ); +} diff --git a/lib/modules/navigation/bindings/navigation_binding.dart b/lib/modules/navigation/bindings/navigation_binding.dart new file mode 100644 index 000000000..3b9b7164d --- /dev/null +++ b/lib/modules/navigation/bindings/navigation_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controllers/navigation_controller.dart'; + +class NavigationBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => NavigationController()); + } +} diff --git a/lib/modules/navigation/controllers/navigation_controller.dart b/lib/modules/navigation/controllers/navigation_controller.dart new file mode 100644 index 000000000..d83a4ee49 --- /dev/null +++ b/lib/modules/navigation/controllers/navigation_controller.dart @@ -0,0 +1,5 @@ +import 'package:get/get.dart'; + +class NavigationController extends GetxController { + static NavigationController get to => Get.find(); //单例访问模式 +} diff --git a/lib/modules/navigation/views/navigation_view.dart b/lib/modules/navigation/views/navigation_view.dart new file mode 100644 index 000000000..9e3f1456a --- /dev/null +++ b/lib/modules/navigation/views/navigation_view.dart @@ -0,0 +1,17 @@ +import 'package:deep_voice/modules/navigation/controllers/navigation_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class NavigationView extends GetView { + const NavigationView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('地图展示')), + body: Stack( + children: [], + ), + ); + } +} diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 0cf57c7b1..44f6e4e81 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -1,3 +1,4 @@ +import 'package:deep_voice/modules/navigation/views/navigation_view.dart'; import 'package:get/get.dart'; import '../modules/feedback/bindings/feedback_bindings.dart'; import '../modules/feedback/views/feedback_view.dart'; @@ -189,5 +190,12 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.navigation, + page: () => const NavigationView(), + binding: AgentBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index ca88986e8..3388ca4af 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -44,4 +44,5 @@ abstract class Routes { // 支持服务 static const feedback = '/feedback'; // 用户反馈 static const agent = '/agent'; // 在线客服 + static const navigation = '/navigation'; // 实时导航 } diff --git a/local_plugins/agent_service/android/build.gradle.kts b/local_plugins/agent_service/android/build.gradle.kts index 79cb9fcde..2a67d0ba0 100644 --- a/local_plugins/agent_service/android/build.gradle.kts +++ b/local_plugins/agent_service/android/build.gradle.kts @@ -47,6 +47,6 @@ dependencies { add("compileOnly", project(":chat_storage")) add("compileOnly", project(":ble_service")) add("compileOnly", project(":music_service")) - // add("compileOnly", project(":amap_service")) + add("compileOnly", project(":amap_service")) add("compileOnly", project(":speech")) } \ No newline at end of file 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 cda7c7df7..c4a38bd0e 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 @@ -7,16 +7,13 @@ import com.yunqiinnovation.azure_speech.AzureAsrHelper import com.yunqiinnovation.open_ai_service.OpenAIService import com.yunqiinnovation.chat_storage.ChatStorageHelper import com.yunqiinnovation.music_service.MusicServiceStarter +import com.yunqiinnovation.amap_service.NavigationServiceHelper import android.util.Log -import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* import java.util.Collections import kotlin.coroutines.CoroutineContext import com.yunqiinnovation.ble_service.BleService import android.media.MediaPlayer -import com.yunqiinnovation.agent_service.R -import android.os.Handler -import android.os.Looper import com.deep_voice.speech.tts.TtsEvent import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventType @@ -1196,7 +1193,6 @@ object AgentService : CoroutineScope { /** * 处理导航 * 将ai返回的音乐连接 在后台启动播放 - * * @param musicUrl 图片文件路径 * @param name 歌曲名称,默认为空 * @param singer 歌手,默认为false @@ -1206,7 +1202,7 @@ object AgentService : CoroutineScope { // 如果 playlist 中有有效的歌曲,开始播放 if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { Log.i(TAG, "启动导航服务 ${start} ${end}") - // NavigationServiceStarter.startServiceWithCommand(context, command = "start", startpos = start, endpos = end) + NavigationServiceHelper.startNavigation(context,"start", startpos = start, endpos = end) } else { Log.i(TAG, "启动导航服务失败") } diff --git a/local_plugins/amap_service/android/build.gradle b/local_plugins/amap_service/android/build.gradle index 4bd0a8a1d..3f126ab46 100644 --- a/local_plugins/amap_service/android/build.gradle +++ b/local_plugins/amap_service/android/build.gradle @@ -65,3 +65,7 @@ android { } } } + +dependencies { + implementation 'androidx.appcompat:appcompat:1.7.0' +} diff --git a/local_plugins/amap_service/android/src/main/AndroidManifest.xml b/local_plugins/amap_service/android/src/main/AndroidManifest.xml index 2b966b420..624e09866 100644 --- a/local_plugins/amap_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/amap_service/android/src/main/AndroidManifest.xml @@ -39,6 +39,14 @@ android:theme="@android:style/Theme.NoTitleBar" android:configChanges="orientation|keyboardHidden|screenSize|navigation" /> + ("endpos") val ntype = call.argument("ntype") if (startpos != null && endpos != null && ntype != null) { - NavigationServiceStarter.startServiceWithCommand(context, "start", startpos, endpos, ntype) + NavigationServiceHelper.startNavigation(context,"start",startpos,endpos,ntype) result.success(null) } else { result.error("INVALID_ARGUMENT", "Missing 'song' map", null) } } + "open" -> { + try { + val startpos = call.argument("startpos") + val endpos = call.argument("endpos") + val ntype = call.argument("ntype") + NavigationServiceHelper.openNavigationUI(startpos,endpos,ntype) + }catch (e: Exception){ + result.error("INVALID_ARGUMENT", "Try Exception ", e) + } + } "stop" -> { - val songs = call.argument>>("songs") - if (songs != null && songs.isNotEmpty()) { - NavigationServiceStarter.startServiceWithCommand(context, "stop") + NavigationServiceHelper.startNavigation(context,"stop") result.success(null) - } else { - result.error("INVALID_ARGUMENT", "Missing or empty 'songs' list", null) - } } - else -> result.notImplemented() + else -> { + result.notImplemented() + } } } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} + + /** + * 实现AgentServiceListener接口 + * 接收来自AgentService的事件,并转发到Flutter + */ + override fun onEvent(eventName: String, data: Map) { + // 将事件转换为JSON字符串 + val eventData = JSONObject().apply { + put("event", eventName) + put("data", JSONObject(data)) + } + + // 在主线程发送事件到Flutter + MainScope().launch { + eventSink?.success(eventData.toString()) + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + // TODO("Not yet implemented") + this.eventSink = events + } + + override fun onCancel(arguments: Any?) { + // TODO("Not yet implemented") + this.eventSink = null + + } + } diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt new file mode 100644 index 000000000..384990bff --- /dev/null +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt @@ -0,0 +1,293 @@ +package com.yunqiinnovation.amap_service + +import android.content.Context +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import com.amap.api.navi.AMapNavi +import com.amap.api.navi.AMapNaviListener +import com.amap.api.navi.AMapNaviView +import com.amap.api.navi.AMapNaviViewListener +import com.amap.api.navi.enums.PathPlanningStrategy +import com.amap.api.navi.model.AMapCalcRouteResult +import com.amap.api.navi.model.AMapLaneInfo +import com.amap.api.navi.model.AMapModelCross +import com.amap.api.navi.model.AMapNaviCameraInfo +import com.amap.api.navi.model.AMapNaviCross +import com.amap.api.navi.model.AMapNaviLocation +import com.amap.api.navi.model.AMapNaviRouteNotifyData +import com.amap.api.navi.model.AMapNaviTrafficFacilityInfo +import com.amap.api.navi.model.AMapServiceAreaInfo +import com.amap.api.navi.model.AimLessModeCongestionInfo +import com.amap.api.navi.model.AimLessModeStat +import com.amap.api.navi.model.NaviInfo +import com.amap.api.navi.model.NaviLatLng +import com.yunqiinnovation.amap_service.NavigationService.Companion.sharedNavi + +class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewListener { + private lateinit var naviView: AMapNaviView + private var navi: AMapNavi? = null + private lateinit var context: Context + private var startpos:String = ""; + private var endpos:String = ""; + private var ntype:String = ""; + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_navigation) + context = this + // 初始化导航视图 + naviView = findViewById(R.id.navi_view) + naviView.onCreate(savedInstanceState) + naviView.setAMapNaviViewListener(this) + + startpos = intent.getStringExtra("startpos").toString() + endpos = intent.getStringExtra("endpos").toString() + ntype = intent.getStringExtra("ntype").toString() + Log.i("NavigationActivity", "onCreate $startpos $endpos $ntype") + navi = NavigationService.sharedNavi + if (navi == null) { + navi = AMapNavi.getInstance(context).apply { + setUseInnerVoice(true) + setBroadcastMode(1) + } + } + navi?.addAMapNaviListener(this); + + // 绑定当前 activity 到导航状态 + } + + override fun onResume() { + super.onResume() + naviView.onResume() + } + + override fun onPause() { + super.onPause() + naviView.onPause() + } + + override fun onDestroy() { + navi?.stopNavi() + navi?.removeAMapNaviListener(this); + AMapNavi.destroy() + NavigationServiceHelper.startNavigation(context,"stop") + naviView.onDestroy() + super.onDestroy() + Log.i("NavigationActivity", "liwei------------------- onDestroy!") + } + + override fun onInitNaviFailure() { +// TODO("Not yet implemented") + } + + override fun onInitNaviSuccess() { + Log.i("NavigationActivity", "onInitNaviSuccess!") + // 起点 + val startParts: List = startpos.split(",") + val startLng = startParts[0].toDouble() + val startLat = startParts[1].toDouble() + + // 终点 + val endParts: List = endpos.split(",") + val endLng = endParts[0].toDouble() + val endLat = endParts[1].toDouble() + + val startList: MutableList = ArrayList() + val endList: MutableList = ArrayList() + startList.add(NaviLatLng(startLat, startLng)) + endList.add(NaviLatLng(endLat, endLng)) + val success = navi?.calculateDriveRoute(startList, endList, null, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT); + if (!success!!) { + Log.e("NavigationActivity", "路径规划请求失败") + } +// TODO("Not yet implemented") + } + + override fun onStartNavi(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onTrafficStatusUpdate() { +// TODO("Not yet implemented") + } + + override fun onLocationChange(p0: AMapNaviLocation?) { +// TODO("Not yet implemented") + } + + override fun onGetNavigationText(p0: Int, p1: String?) { +// TODO("Not yet implemented") + } + + override fun onGetNavigationText(p0: String?) { +// TODO("Not yet implemented") + } + + override fun onEndEmulatorNavi() { +// TODO("Not yet implemented") + } + + override fun onArriveDestination() { +// TODO("Not yet implemented") + } + + override fun onCalculateRouteFailure(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) { +// TODO("Not yet implemented") + } + + override fun onReCalculateRouteForYaw() { +// TODO("Not yet implemented") + } + + override fun onReCalculateRouteForTrafficJam() { +// TODO("Not yet implemented") + } + + override fun onArrivedWayPoint(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onGpsOpenStatus(p0: Boolean) { +// TODO("Not yet implemented") + } + + override fun onNaviInfoUpdate(p0: NaviInfo?) { +// TODO("Not yet implemented") + } + + override fun updateCameraInfo(p0: Array?) { +// TODO("Not yet implemented") + } + + override fun updateIntervalCameraInfo( + p0: AMapNaviCameraInfo?, + p1: AMapNaviCameraInfo?, + p2: Int + ) { +// TODO("Not yet implemented") + } + + override fun onServiceAreaUpdate(p0: Array?) { +// TODO("Not yet implemented") + } + + override fun showCross(p0: AMapNaviCross?) { +// TODO("Not yet implemented") + } + + override fun hideCross() { +// TODO("Not yet implemented") + } + + override fun showModeCross(p0: AMapModelCross?) { +// TODO("Not yet implemented") + } + + override fun hideModeCross() { +// TODO("Not yet implemented") + } + + override fun showLaneInfo(p0: Array?, p1: ByteArray?, p2: ByteArray?) { +// TODO("Not yet implemented") + } + + override fun showLaneInfo(p0: AMapLaneInfo?) { +// TODO("Not yet implemented") + } + + override fun hideLaneInfo() { +// TODO("Not yet implemented") + } + + override fun onCalculateRouteSuccess(p0: IntArray?) { + navi?.startNavi(1); +// TODO("Not yet implemented") + } + + override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { +// TODO("Not yet implemented") + } + + override fun notifyParallelRoad(p0: Int) { +// TODO("Not yet implemented") + } + + override fun OnUpdateTrafficFacility(p0: Array?) { +// TODO("Not yet implemented") + } + + override fun OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) { +// TODO("Not yet implemented") + } + + override fun updateAimlessModeStatistics(p0: AimLessModeStat?) { +// TODO("Not yet implemented") + } + + override fun updateAimlessModeCongestionInfo(p0: AimLessModeCongestionInfo?) { +// TODO("Not yet implemented") + } + + override fun onPlayRing(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onNaviRouteNotify(p0: AMapNaviRouteNotifyData?) { +// TODO("Not yet implemented") + } + + override fun onGpsSignalWeak(p0: Boolean) { +// TODO("Not yet implemented") + } + + //UI事件---------------------------------------------------------------------------------------------- + override fun onNaviSetting() { +// TODO("Not yet implemented") + } + + override fun onNaviCancel() { + finishAndRemoveTask() +// TODO("Not yet implemented") + } + + override fun onNaviBackClick(): Boolean { +// TODO("Not yet implemented") + return false + } + + override fun onNaviMapMode(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onNaviTurnClick() { +// TODO("Not yet implemented") + } + + override fun onNextRoadClick() { +// TODO("Not yet implemented") + } + + override fun onScanViewButtonClick() { +// TODO("Not yet implemented") + } + + override fun onLockMap(p0: Boolean) { +// TODO("Not yet implemented") + } + + override fun onNaviViewLoaded() { +// TODO("Not yet implemented") + } + + override fun onMapTypeChanged(p0: Int) { +// TODO("Not yet implemented") + } + + override fun onNaviViewShowMode(p0: Int) { +// TODO("Not yet implemented") + } +} diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt index 04b4e07b7..dceab4f76 100644 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt @@ -5,13 +5,15 @@ import android.app.NotificationManager import android.app.Service import android.content.Context import android.content.Intent +import android.graphics.Bitmap import android.os.Build import android.os.IBinder +import android.util.Base64 import android.util.Log import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import com.amap.api.navi.AMapNavi import com.amap.api.navi.AMapNaviListener -import com.amap.api.navi.enums.NaviType import com.amap.api.navi.enums.PathPlanningStrategy import com.amap.api.navi.model.AMapCalcRouteResult import com.amap.api.navi.model.AMapLaneInfo @@ -26,11 +28,12 @@ import com.amap.api.navi.model.AimLessModeCongestionInfo import com.amap.api.navi.model.AimLessModeStat import com.amap.api.navi.model.NaviInfo import com.amap.api.navi.model.NaviLatLng +import java.io.ByteArrayOutputStream import java.io.FileNotFoundException import java.util.concurrent.atomic.AtomicBoolean -class NavigationService: Service() { +class NavigationService: Service(),AMapNaviListener { companion object { private const val TAG = "NavigationService" @@ -42,46 +45,57 @@ class NavigationService: Service() { private var startpos: String = "" private var endpos: String = "" private var naviType: String? = null - private var playlist = mutableListOf>() - private var currentIndex = 0 - + private var lastNotificationUpdateTime = 0L + private val notificationUpdateInterval = 1000L + var instance: NavigationService? = null + var sharedNavi: AMapNavi? = null fun isServiceRunning() = isRunning.get() } - private lateinit var aMapNavi: AMapNavi - override fun onCreate() { super.onCreate() + instance = this startForegroundNotification() } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { val command = intent.getStringExtra("command") ?: return START_NOT_STICKY when (command) { - "start" -> { + "start" ->{ try { startpos = intent.getStringExtra("startpos").toString() endpos = intent.getStringExtra("endpos").toString() naviType = intent.getStringExtra("naviType"); Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") - // 初始化导航 - aMapNavi = AMapNavi.getInstance(applicationContext) - aMapNavi.setUseInnerVoice(true) - aMapNavi.setBroadcastMode(1) - aMapNavi.addAMapNaviListener(naviListener) + if (startpos.isEmpty() || endpos.isEmpty()) { + Log.e(TAG, "Missing start or end position") + return START_NOT_STICKY + } + initializeAMapNavi() }catch (e: FileNotFoundException){ Log.w("NavigationService", "Custom voice bin not found: ${e.message}") } } "stop" -> { - aMapNavi.stopNavi(); + stopNavigation() + return START_NOT_STICKY } } return START_STICKY } - + private fun initializeAMapNavi() { + try { + sharedNavi = AMapNavi.getInstance(applicationContext).apply { + setUseInnerVoice(true) + setBroadcastMode(1) + addAMapNaviListener(this@NavigationService) + } + } catch (e: Exception) { + Log.e(TAG, "初始化AMapNavi失败: ${e.message}", e) + } + } private fun startForegroundNotification() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( @@ -106,209 +120,283 @@ class NavigationService: Service() { startForeground(NOTIFICATION_ID, notification) } - // 实现监听器,处理导航播报等 - private val naviListener = object : AMapNaviListener { - override fun onInitNaviFailure() { - Log.e(TAG, "onInitNaviFailure!") -// TODO("Not yet implemented") - } + private fun updateNotification(contentText: String, iconBitmap: Bitmap?) { + val builder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("导航进行中") + .setContentText(contentText) + .setSmallIcon(R.drawable.ic_navigation) // 通知栏图标(小) + .setOnlyAlertOnce(true) + .setOngoing(true) - override fun onInitNaviSuccess() { - Log.e(TAG, "onInitNaviSuccess!") - // 起点 - val startParts: List = startpos.split(",") - val startLng = startParts[0].toDouble() - val startLat = startParts[1].toDouble() - - // 终点 - val endParts: List = endpos.split(",") - val endLng = endParts[0].toDouble() - val endLat = endParts[1].toDouble() - - val startList: MutableList = ArrayList() - val endList: MutableList = ArrayList() - startList.add(NaviLatLng(startLat, startLng)) - endList.add(NaviLatLng(endLat, endLng)) - val success = aMapNavi.calculateDriveRoute(startList, endList, null, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT); - if (!success) { - Log.e(TAG, "路径规划请求失败") - } + if (iconBitmap != null) { + val tinted = tintBitmap(iconBitmap, ContextCompat.getColor(this, R.color.teal_200)) + builder.setLargeIcon(tinted) } - override fun onStartNavi(p0: Int) { - Log.e(TAG, "onStartNavi!") -// TODO("Not yet implemented") - } + val notification = builder.build() + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.notify(NOTIFICATION_ID, notification) + } + private fun tintBitmap(source: Bitmap, tintColor: Int): Bitmap { + val result = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888) + val canvas = android.graphics.Canvas(result) + val paint = android.graphics.Paint() + paint.colorFilter = android.graphics.PorterDuffColorFilter(tintColor, android.graphics.PorterDuff.Mode.SRC_IN) + canvas.drawBitmap(source, 0f, 0f, paint) + return result + } + fun bitmapToBase64(bitmap: Bitmap?): String? { + if (bitmap != null) return null; + val outputStream = ByteArrayOutputStream() + bitmap?.compress(Bitmap.CompressFormat.PNG, 100, outputStream) + val byteArray = outputStream.toByteArray() + return Base64.encodeToString(byteArray, Base64.NO_WRAP) + } - override fun onTrafficStatusUpdate() { - Log.e(TAG, "onTrafficStatusUpdate!") -// TODO("Not yet implemented") + private fun stopNavigation() { + if (sharedNavi != null) { + sharedNavi?.stopNavi() + sharedNavi?.removeAMapNaviListener(this) + sharedNavi = null } + isRunning.set(false) + stopSelf() + } - override fun onLocationChange(location: AMapNaviLocation) { - Log.e(TAG, "onLocationChange! ${location}") - // 可通过广播发送位置信息到 Flutter - } + override fun onBind(intent: Intent?): IBinder? = null - override fun onGetNavigationText(p0: Int, p1: String?) { - Log.e(TAG, "onGetNavigationText! $p0 $p1") -// TODO("Not yet implemented") - } - override fun onGetNavigationText(p0: String?) { - Log.e(TAG, "onGetNavigationText! $p0") -// TODO("Not yet implemented") - } + override fun onInitNaviFailure() { + Log.e(TAG, "onInitNaviFailure!") +// TODO("Not yet implemented") + } - override fun onEndEmulatorNavi() { - Log.e(TAG, "onEndEmulatorNavi! ") -// TODO("Not yet implemented") - } + override fun onInitNaviSuccess() { + Log.e(TAG, "onInitNaviSuccess!") + // 起点 + val startParts: List = startpos.split(",") + val startLng = startParts[0].toDouble() + val startLat = startParts[1].toDouble() + + // 终点 + val endParts: List = endpos.split(",") + val endLng = endParts[0].toDouble() + val endLat = endParts[1].toDouble() + + val startList: MutableList = ArrayList() + val endList: MutableList = ArrayList() + startList.add(NaviLatLng(startLat, startLng)) + endList.add(NaviLatLng(endLat, endLng)) + val success = sharedNavi?.calculateDriveRoute(startList, endList, null, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT); + if (!success!!) { + Log.e(TAG, "路径规划请求失败") + } +// TODO("Not yet implemented") + } - override fun onArriveDestination() { - Log.e(TAG, "onArriveDestination! ") -// TODO("Not yet implemented") - } + override fun onStartNavi(p0: Int) { + Log.e(TAG, "onStartNavi! $p0") +// TODO("Not yet implemented") + } - override fun onCalculateRouteFailure(p0: Int) { - Log.e(TAG, "路径规划失败, 错误码: $p0") - // TODO: 可添加反馈机制给 Flutter 层或用户提示 - } + override fun onTrafficStatusUpdate() { + Log.e(TAG, "onTrafficStatusUpdate!") +// TODO("Not yet implemented") + } - override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) { - Log.e(TAG, "onCalculateRouteFailure! ${p0}") -// TODO("Not yet implemented") - } + override fun onLocationChange(p0: AMapNaviLocation?) { + Log.e(TAG, "onLocationChange! $p0") +// TODO("Not yet implemented") + } - override fun onReCalculateRouteForYaw() { - Log.e(TAG, "onReCalculateRouteForYaw!") -// TODO("Not yet implemented") - } + override fun onGetNavigationText(p0: Int, p1: String?) { + Log.e(TAG, "onGetNavigationText! $p0 $p1") +// TODO("Not yet implemented") + } - override fun onReCalculateRouteForTrafficJam() { - Log.e(TAG, "onReCalculateRouteForTrafficJam!") -// TODO("Not yet implemented") - } + override fun onGetNavigationText(p0: String?) { + Log.e(TAG, "onGetNavigationText! $p0") +// TODO("Not yet implemented") + } - override fun onArrivedWayPoint(p0: Int) { - Log.e(TAG, "onArrivedWayPoint!") -// TODO("Not yet implemented") - } + override fun onEndEmulatorNavi() { + Log.e(TAG, "onEndEmulatorNavi!") + stopNavigation() +// TODO("Not yet implemented") + } - override fun onGpsOpenStatus(p0: Boolean) { - Log.e(TAG, "onGpsOpenStatus!") -// TODO("Not yet implemented") - } + override fun onArriveDestination() { + Log.e(TAG, "onArriveDestination!") + stopNavigation() +// TODO("Not yet implemented") + } - override fun onNaviInfoUpdate(p0: NaviInfo?) { - Log.e(TAG, "onNaviInfoUpdate! $p0") -// TODO("Not yet implemented") - } + override fun onCalculateRouteFailure(p0: Int) { + Log.e(TAG, "onCalculateRouteFailure! $p0") +// TODO("Not yet implemented") + } - override fun updateCameraInfo(p0: Array?) { - Log.e(TAG, "updateCameraInfo! $p0") -// TODO("Not yet implemented") - } + override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) { + Log.e(TAG, "onCalculateRouteFailure! $p0") +// TODO("Not yet implemented") + } - override fun updateIntervalCameraInfo( - p0: AMapNaviCameraInfo?, - p1: AMapNaviCameraInfo?, - p2: Int - ) { - Log.e(TAG, "updateIntervalCameraInfo! $p0") -// TODO("Not yet implemented") - } + override fun onReCalculateRouteForYaw() { + Log.e(TAG, "onReCalculateRouteForYaw!") +// TODO("Not yet implemented") + } - override fun onServiceAreaUpdate(p0: Array?) { - Log.e(TAG, "onServiceAreaUpdate! $p0") -// TODO("Not yet implemented") - } + override fun onReCalculateRouteForTrafficJam() { + Log.e(TAG, "onReCalculateRouteForTrafficJam!") +// TODO("Not yet implemented") + } - override fun showCross(p0: AMapNaviCross?) { - Log.e(TAG, "showCross! $p0") -// TODO("Not yet implemented") - } + override fun onArrivedWayPoint(p0: Int) { + Log.e(TAG, "onArrivedWayPoint! $p0") +// TODO("Not yet implemented") + } - override fun hideCross() { - Log.e(TAG, "hideCross!") -// TODO("Not yet implemented") - } + override fun onGpsOpenStatus(p0: Boolean) { + Log.e(TAG, "onGpsOpenStatus! $p0") +// TODO("Not yet implemented") + } - override fun showModeCross(p0: AMapModelCross?) { - Log.e(TAG, "showModeCross! $p0") -// TODO("Not yet implemented") - } + override fun onNaviInfoUpdate(naviInfo: NaviInfo?) { + Log.e(TAG, "onNaviInfoUpdate! $naviInfo") + if (naviInfo == null) return + + val currentTime = System.currentTimeMillis() + if (currentTime - lastNotificationUpdateTime < notificationUpdateInterval) { + // 距离上次更新不足1秒,跳过这次更新 + return + } + val directionBitmap = naviInfo.iconBitmap + val currentRoad = naviInfo.currentRoadName + val nextRoad = naviInfo.nextRoadName + val remainingDistance = naviInfo.getCurStepRetainDistance() + val remainingTime = naviInfo.getPathRetainTime() + val distanceText = if (remainingDistance < 1000) { + "${remainingDistance} 米" + } else { + String.format("%.1f 千米", remainingDistance / 1000.0) + } + val contentText = "$distanceText \n进入:$nextRoad" +// val directionBitmapBase64 = bitmapToBase64(directionBitmap) +// val naviInfoMap: Map = mapOf( +// "currentRoad" to currentRoad, +// "nextRoad" to nextRoad, +// "distanceText" to distanceText, +// "directionIcon" to (directionBitmapBase64 ?: "") +// ) +// NavigationServiceHelper.sendEvent("amap_update",naviInfoMap) + updateNotification(contentText, directionBitmap) + Log.e(TAG, "onNaviInfoUpdate! $contentText") +// TODO("Not yet implemented") + } - override fun hideModeCross() { - Log.e(TAG, "hideModeCross!") -// TODO("Not yet implemented") - } + override fun updateCameraInfo(p0: Array?) { + Log.e(TAG, "updateCameraInfo! $p0") +// TODO("Not yet implemented") + } - override fun showLaneInfo(p0: Array?, p1: ByteArray?, p2: ByteArray?) { - Log.e(TAG, "showLaneInfo! $p0") -// TODO("Not yet implemented") - } + override fun updateIntervalCameraInfo( + p0: AMapNaviCameraInfo?, + p1: AMapNaviCameraInfo?, + p2: Int + ) { + Log.e(TAG, "updateIntervalCameraInfo! $p0 $p1 $p2") +// TODO("Not yet implemented") + } - override fun showLaneInfo(p0: AMapLaneInfo?) { - Log.e(TAG, "showLaneInfo! $p0") -// TODO("Not yet implemented") - } + override fun onServiceAreaUpdate(p0: Array?) { + Log.e(TAG, "onServiceAreaUpdate! $p0") +// TODO("Not yet implemented") + } - override fun hideLaneInfo() { - Log.e(TAG, "hideLaneInfo!") -// TODO("Not yet implemented") - } + override fun showCross(p0: AMapNaviCross?) { + Log.e(TAG, "showCross!") +// TODO("Not yet implemented") + } - override fun onCalculateRouteSuccess(ids: IntArray?) { - Log.e(TAG, "onCalculateRouteSuccess! $ids") - aMapNavi.startNavi(NaviType.EMULATOR) - } + override fun hideCross() { + Log.e(TAG, "hideCross!") +// TODO("Not yet implemented") + } - override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { - Log.e(TAG, "onCalculateRouteSuccess! $p0") -// TODO("Not yet implemented") - } + override fun showModeCross(p0: AMapModelCross?) { + Log.e(TAG, "showModeCross! $p0") +// TODO("Not yet implemented") + } - override fun notifyParallelRoad(p0: Int) { - Log.d(TAG, "是否在拥挤平行路段: $p0") -// TODO("Not yet implemented") - } + override fun hideModeCross() { + Log.e(TAG, "hideModeCross!") +// TODO("Not yet implemented") + } - override fun OnUpdateTrafficFacility(p0: Array?) { - Log.d(TAG, "OnUpdateTrafficFacility: $p0") -// TODO("Not yet implemented") - } + override fun showLaneInfo(p0: Array?, p1: ByteArray?, p2: ByteArray?) { + Log.e(TAG, "showLaneInfo! $p0 $p1 $p2") +// TODO("Not yet implemented") + } - override fun OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) { - Log.d(TAG, "OnUpdateTrafficFacility: $p0") -// TODO("Not yet implemented") - } + override fun showLaneInfo(p0: AMapLaneInfo?) { + Log.e(TAG, "showLaneInfo! $p0") +// TODO("Not yet implemented") + } - override fun updateAimlessModeStatistics(p0: AimLessModeStat?) { -// TODO("Not yet implemented") - Log.d(TAG, "updateAimlessModeStatistics: $p0") - } + override fun hideLaneInfo() { + Log.e(TAG, "hideLaneInfo!") +// TODO("Not yet implemented") + } - override fun updateAimlessModeCongestionInfo(p0: AimLessModeCongestionInfo?) { -// TODO("Not yet implemented") - Log.d(TAG, "updateAimlessModeCongestionInfo: $p0") - } + override fun onCalculateRouteSuccess(p0: IntArray?) { + Log.e(TAG, "onCalculateRouteSuccess! $p0") + sharedNavi?.startNavi(1); +// TODO("Not yet implemented") + } - override fun onPlayRing(p0: Int) { - Log.d(TAG, "onPlayRing: $p0") -// TODO("Not yet implemented") - } + override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { + sharedNavi?.startNavi(2); + Log.e(TAG, "onCalculateRouteSuccess! $p0") +// TODO("Not yet implemented") + } - override fun onNaviRouteNotify(p0: AMapNaviRouteNotifyData?) { - Log.w(TAG, "GPS信号弱") -// TODO("Not yet implemented") - } + override fun notifyParallelRoad(p0: Int) { + Log.e(TAG, "notifyParallelRoad! $p0") +// TODO("Not yet implemented") + } - override fun onGpsSignalWeak(p0: Boolean) { - Log.w(TAG, "GPS信号弱") -// TODO("Not yet implemented") - } + override fun OnUpdateTrafficFacility(p0: Array?) { + Log.e(TAG, "OnUpdateTrafficFacility! $p0") +// TODO("Not yet implemented") } - override fun onBind(intent: Intent?): IBinder? = null + override fun OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) { + Log.e(TAG, "OnUpdateTrafficFacility! $p0") +// TODO("Not yet implemented") + } + + override fun updateAimlessModeStatistics(p0: AimLessModeStat?) { + Log.e(TAG, "updateAimlessModeStatistics! $p0") +// TODO("Not yet implemented") + } + + override fun updateAimlessModeCongestionInfo(p0: AimLessModeCongestionInfo?) { + Log.e(TAG, "updateAimlessModeCongestionInfo! $p0") +// TODO("Not yet implemented") + } + + override fun onPlayRing(p0: Int) { + Log.e(TAG, "onPlayRing! $p0") +// TODO("Not yet implemented") + } + + override fun onNaviRouteNotify(p0: AMapNaviRouteNotifyData?) { + Log.e(TAG, "onNaviRouteNotify! $p0") +// TODO("Not yet implemented") + } + + override fun onGpsSignalWeak(p0: Boolean) { + Log.e(TAG, "onGpsSignalWeak! $p0") +// TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt new file mode 100644 index 000000000..500e45bf7 --- /dev/null +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt @@ -0,0 +1,108 @@ +package com.yunqiinnovation.amap_service + +import android.content.Context +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.core.content.ContextCompat +import java.util.Collections + +/** + * 代理服务事件监听接口 + */ +interface NavigationListener { + /** + * 当有事件发生时调用此方法 + * + * @param eventName 事件名称 + * @param data 事件数据 + */ + fun onEvent(eventName: String, data: Map) +} + +object NavigationServiceHelper { + private val TAG = "NavigationServiceStarter" + private lateinit var context: Context + private val listeners = Collections.synchronizedSet(mutableSetOf()) + fun init(appContext: Context) { + context = appContext + } + //启动导航 + fun startNavigation(context: Context,command:String, startpos: String? = null, endpos: String? = null, ntype: String? = null) { + Log.i(TAG, "liwei------------------- startNavigation: $command $startpos $endpos $ntype") + val intent = Intent(context, NavigationService::class.java).apply { + putExtra("command", command) + startpos?.let { putExtra("startpos", it) } + endpos?.let { putExtra("endpos", it) } + ntype?.let { putExtra("ntype", it) } + } + ContextCompat.startForegroundService(context, intent) +// context.startForegroundService(intent) + } + + //打开导航页面 + fun openNavigationUI(startpos: String? = null, endpos: String? = null, ntype: String? = null) { + Log.i(TAG, "liwei------------------- openNavigationUI: $startpos $endpos $ntype") + val intent = Intent(context, NavigationActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra("startpos", startpos) + putExtra("endpos", endpos) + putExtra("ntype", ntype) + } + context.startActivity(intent) + } + + /** + * 添加事件监听器 + * @param listener 要添加的监听器 + */ + fun addListener(listener: NavigationListener) { + listeners.add(listener) + } + + /** + * 移除事件监听器 + * @param listener 要移除的监听器 + */ + fun removeListener(listener: NavigationListener) { + listeners.remove(listener) + } + + /** + * 移除所有事件监听器 + */ + fun clearListeners() { + listeners.clear() + } + + /** + * 发送事件 + */ + fun sendEvent(eventName: String, data: Map) { + // 检查当前是否在主线程 + if (Looper.myLooper() == Looper.getMainLooper()) { + // 已在主线程,直接发送 + listeners.forEach { listener -> + try { + listener.onEvent(eventName, data) + } catch (e: Exception) { +// FileLogger.e(NavigationService.TAG, "向监听器发送事件失败", e) + } + } + + } else { + // 不在主线程,切换到主线程 + Handler(Looper.getMainLooper()).post { + listeners.forEach { listener -> + try { + listener.onEvent(eventName, data) + } catch (e: Exception) { +// FileLogger.e(NavigationService.TAG, "向监听器发送事件失败", e) + } + } + } + } + } + +} \ No newline at end of file diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt deleted file mode 100644 index 788495e9b..000000000 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.yunqiinnovation.amap_service - -import android.content.Context -import android.content.Intent -import android.util.Log -import androidx.core.content.ContextCompat - -object NavigationServiceStarter { - private val TAG = "NavigationServiceStarter" - //播放单曲 - fun startServiceWithCommand(context: Context, command: String, startpos: String? = null, endpos: String? = null, ntype: String? = null) { - Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $startpos $endpos $ntype") - val intent = Intent(context, NavigationService::class.java).apply { - putExtra("command", command) - startpos?.let { - putExtra("startpos",it) // 只有非 null 时才 putExtra - } - endpos?.let { - putExtra("endpos", it) // 只有非 null 时才 putExtra - } - ntype?.let { - putExtra("ntype",it) // 只有非 null 时才 putExtra - } - } - val success = try { - ContextCompat.startForegroundService(context, intent) - true - } catch (e: Exception) { - Log.e(TAG, "liwei------------------- Failed to start service", e) - false - } - Log.d(TAG, "liwei------------------- Service start success: $success") - } -} \ No newline at end of file diff --git a/local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml b/local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml index 33bf89e9e..3e0d93ceb 100644 --- a/local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml +++ b/local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml @@ -1,9 +1,9 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="#000000" + android:pathData="M12,2C8.13,2 5,5.13 5,9C5,11.74 7.19,14.74 9.93,16.07L12,21L14.07,16.07C16.81,14.74 19,11.74 19,9C19,5.13 15.87,2 12,2ZM12,12C10.34,12 9,10.66 9,9C9,7.34 10.34,6 12,6C13.66,6 15,7.34 15,9C15,10.66 13.66,12 12,12Z"/> diff --git a/local_plugins/amap_service/android/src/main/res/layout/activity_navigation.xml b/local_plugins/amap_service/android/src/main/res/layout/activity_navigation.xml new file mode 100644 index 000000000..1d524c300 --- /dev/null +++ b/local_plugins/amap_service/android/src/main/res/layout/activity_navigation.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/local_plugins/amap_service/android/src/main/res/values/colors.xml b/local_plugins/amap_service/android/src/main/res/values/colors.xml new file mode 100644 index 000000000..f87690ccd --- /dev/null +++ b/local_plugins/amap_service/android/src/main/res/values/colors.xml @@ -0,0 +1,20 @@ + + + + #6200EE + #3700B3 + #03DAC5 + #018786 + #000000 + #FFFFFF + + + #F44336 + #4CAF50 + #2196F3 + + #6200EE + #3700B3 + #03DAC5 + + diff --git a/local_plugins/amap_service/android/src/main/res/values/styles.xml b/local_plugins/amap_service/android/src/main/res/values/styles.xml new file mode 100644 index 000000000..7418479ed --- /dev/null +++ b/local_plugins/amap_service/android/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + diff --git a/local_plugins/amap_service/lib/amap_service.dart b/local_plugins/amap_service/lib/amap_service.dart index f3b8e1122..b90fd5434 100644 --- a/local_plugins/amap_service/lib/amap_service.dart +++ b/local_plugins/amap_service/lib/amap_service.dart @@ -1,5 +1,38 @@ +import 'dart:convert'; + import 'package:flutter/services.dart'; +/// 代理服务事件类型 +enum AmapServiceEventType { + /// 导航启动 + starte, + + ///导航结束 + stope, + + update, + + /// 错误 + error, + + /// 未知事件 + unknown, +} + +/// 代理服务事件数据 +class AmapServiceEvent { + /// 事件类型 + final AmapServiceEventType type; + + /// 事件数据 + final Map data; + + AmapServiceEvent({required this.type, required this.data}); + + @override + String toString() => 'AmapServiceEvent(type: $type, data: $data)'; +} + class AmapService { static final AmapService _instance = AmapService._internal(); @@ -12,48 +45,70 @@ class AmapService { ); // 事件通道 - 简化为只使用数据和状态通道 - late final EventChannel _dataEventChannel; - late final EventChannel _statusEventChannel; - - // 事件流 - late final Stream _dataStream; - late final Stream _statusStream; - - // 获取事件流 - Stream get onDataReceived => _dataStream; - - // 状态流直接传递原始数据 - Stream get onStatusUpdated => _statusStream; + late final EventChannel _eventChannel; + static Stream? _eventStream; // 内部构造函数 AmapService._internal() { - _dataEventChannel = const EventChannel( - 'com.yunqiinnovation.amap_service/data_events', - ); - _statusEventChannel = const EventChannel( - 'com.yunqiinnovation.amap_service/status_events', + _eventChannel = const EventChannel( + 'com.yunqiinnovation.amap_service/events', ); + } + + /// 获取事件流 + Stream get events { + _eventStream ??= _eventChannel.receiveBroadcastStream().map((event) { + try { + final Map eventMap = jsonDecode(event); + final String eventName = eventMap['event']; + final Map eventData = eventMap['data']; - // 初始化事件流 - _dataStream = _dataEventChannel.receiveBroadcastStream(); - _statusStream = _statusEventChannel.receiveBroadcastStream(); + // 将事件名称转换为枚举 + final AmapServiceEventType type = _stringToEventType(eventName); + + return AmapServiceEvent(type: type, data: eventData); + } catch (e) { + return AmapServiceEvent( + type: AmapServiceEventType.unknown, + data: {'error': e.toString(), 'rawEvent': event}, + ); + } + }); + + return _eventStream!; } - // 初始化SDK - Future initSdk({String? iosKey, String? androidKey}) async {} - // 定位初始化 - Future initLocation() async {} + /// 将字符串事件名转换为枚举类型 + static AmapServiceEventType _stringToEventType(String eventName) { + switch (eventName) { + case 'amap_starte': + return AmapServiceEventType.starte; + case 'amap_stope': + return AmapServiceEventType.stope; + case 'amap_update': + return AmapServiceEventType.update; + case 'amap_error': + return AmapServiceEventType.error; + default: + return AmapServiceEventType.unknown; + } + } - // 开始定位 - Future startLocation() async {} + Future start() async { + await _methodChannel.invokeMethod('start'); + } - // 监听定位返回位置 - Stream locationStream() { - return _dataStream; + Future open(String start, String end, String ntype) async { + await _methodChannel.invokeMethod('open', { + 'startpos': start, + "endpos": end, + "ntype": ntype, + }); } - // 停止定位 - Future stopLocation() async {} + Future stop() async { + await _methodChannel.invokeMethod('stop'); + } // 销毁 Future dispose() async {} diff --git a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt index b491eb1bd..d56f3d77d 100644 --- a/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt +++ b/local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/SystemFunctionHandler.kt @@ -29,6 +29,7 @@ import android.content.ContentUris import androidx.core.content.ContextCompat import android.app.ActivityManager import android.app.Application +import android.content.ComponentName import android.os.Process /** * 系统功能处理器 - 负责调用系统API执行各种功能以及注册系统功能 @@ -77,7 +78,9 @@ class SystemFunctionHandler(private val context: Context) { // 注册打开录音机功能 registerOpenRecorderFunction(client) - + + // 注册关闭导航服务功能 + registerStopNavigationFunction(client) Log.d(TAG, "所有系统功能注册完成") } catch (e: Exception) { Log.e(TAG, "注册系统功能失败: ${e.message}", e) @@ -443,7 +446,29 @@ class SystemFunctionHandler(private val context: Context) { ) Log.d(TAG, "已注册打开录音机功能") } - + /** + * 注册获取当前时间函数 + */ + private fun registerStopNavigationFunction(client: MCPClient) { + client.registerLocalFunction( + "stop_navigation", + "结束当前导航服务", + JSONObject(""" + { + "type": "object", + "properties": {}, + "required": [] + } + """), + object : FunctionHandler { + override suspend fun handle(arguments: Map): String { + val result = stopNavigation() + return "{\"result\": \"${result.message}\"}" + } + } + ) + Log.d(TAG, "已注册获取当前时间功能") + } /** * 控制媒体播放 * @param action 操作类型:"play", "pause", "previous", "next" @@ -1270,7 +1295,31 @@ class SystemFunctionHandler(private val context: Context) { false } } - + + + /** + * 停止当前导航 + * @return 操作结果 + */ + fun stopNavigation():Result{ + Log.d(TAG, "结束当前导航服务") + try { +// val intent = Intent() +// intent.component = ComponentName("com.example.plugin", "com.example.plugin.MyBackgroundService") +// stopService(intent) + return Result( + success = true, + message = "当前导航服务已结束" + ) + } catch (e: Exception) { + Log.e(TAG, "结束当前导航服务失败: ${e.message}", e) + return Result( + success = false, + message = "结束当前导航服务失败: ${e.message}" + ) + } + } + /** * 操作结果数据类 */ diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties index 9139b185a..c231039ee 100644 --- a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties +++ b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties @@ -1,2 +1,2 @@ -#Sun May 18 21:39:38 IST 2025 -gradle.version=8.10 +#Wed May 21 01:03:35 CST 2025 +gradle.version=8.9 diff --git a/pubspec.yaml b/pubspec.yaml index cb2913215..94783cbd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,6 +85,8 @@ dependencies: path: local_plugins/agent_service music_service: path: local_plugins/music_service + amap_service: + path: local_plugins/amap_service dev_dependencies: