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/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index f7b2ddbd0..d5da0ef00 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,6 +22,10 @@ class NavigationManager extends GetxService { super.onClose(); } + Stream events() { + return _amapervice.events; + } + //导航 Future navigation(String org, String dis) async { // await _amapervice.navigation(org, dis); diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 536514dbf..33e76272d 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'; @@ -45,6 +48,7 @@ class AgentController extends GetxController { //音乐播放管理 final _musiceManager = Get.find(); + final _navigationManager = Get.find(); var isPlaying = false.obs; var currentUrl = "".obs; @@ -72,6 +76,9 @@ class AgentController extends GetxController { // 流事件订阅 StreamSubscription? _eventSubscription; + // 导航流事件订阅 + StreamSubscription? _amapeventSubscription; + // 用于跟踪AI回复状态 bool _isNewAssistantResponse = true; String _currentResponseId = ''; @@ -395,12 +402,12 @@ class AgentController extends GetxController { Logger.e(TAG, '解析meta数据失败: $e'); } } - if(metaResult.isNotEmpty){ - + if (metaResult.isNotEmpty) { // 判断是否为新的回复或响应ID是否改变 if (_isNewAssistantResponse || _currentResponseId != responseId) { // 添加新消息 - messages.add(Message(isUser: false, text: "", meta: metaResult)); + messages + .add(Message(isUser: false, text: "", meta: metaResult)); _isNewAssistantResponse = false; _currentResponseId = responseId; } else { @@ -422,6 +429,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; + } + }); } // 发送文本消息 @@ -535,24 +594,22 @@ class AgentController extends GetxController { try { // 创建待请求权限列表 List permissionsToRequest = []; - + if (Platform.isAndroid) { // Android 需要考虑SDK版本不同的权限要求 permissionsToRequest.add(Permission.microphone); - - } else if (Platform.isIOS) { // iOS 只需要麦克风权限 permissionsToRequest.add(Permission.microphone); } - + // 批量请求所有需要的权限 final statuses = await permissionsToRequest.request(); - + // 检查是否所有权限都已授予 bool allGranted = true; Permission? deniedPermission; - + statuses.forEach((permission, status) { if (!status.isGranted) { allGranted = false; @@ -560,33 +617,33 @@ class AgentController extends GetxController { Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status'); } }); - + // 单独检查麦克风权限状态,因为这是主要需求 final micStatus = await Permission.microphone.status; - + // 如果所有权限都已授予,或至少麦克风权限已授予,返回true if (allGranted || micStatus.isGranted) { return true; } - + // 处理特定权限被永久拒绝的情况 if (micStatus.isPermanentlyDenied) { _showPermissionDialog(true); return false; } - + // 普通拒绝情况 if (deniedPermission != null) { _showPermissionDialog(false); } - + return false; } catch (e) { Logger.e(TAG, '检查麦克风权限失败: $e'); return false; } } - + // 获取Android SDK版本 Future _getAndroidSdkVersion() async { if (Platform.isAndroid) { @@ -790,4 +847,9 @@ class AgentController extends GetxController { AgentService.processTextInput(messages[index].text, speakResponse: isTtsEnabled.value); } + + //去导航 + void gotoNavigation() { + Get.toNamed(Routes.navigation); + } } diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 589b18b4a..ecce875e8 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -285,8 +285,12 @@ class MessageBubble extends GetView { } else if (meta!.keys.contains('card_navigation')) { 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, 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 index 8ee1cd4bd..129fd8f32 100644 --- a/lib/modules/agent/views/message_bubble_navigation.dart +++ b/lib/modules/agent/views/message_bubble_navigation.dart @@ -1,10 +1,14 @@ +import 'dart:convert'; +import 'package:deep_voice/routes/app_routes.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; /// A widget that displays a navigation card with different modes of transportation. Widget navigationCard( BuildContext context, { required List> modes, - double height = 260, + Map? navigation, + double height = 300, }) { int currentIndex = 0; @@ -24,6 +28,7 @@ Widget navigationCard( child: StatefulBuilder( builder: (context, setInnerState) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ // 路线图片 ClipRRect( @@ -56,6 +61,7 @@ Widget navigationCard( width: 50, child: Text( modes[index]["ntype"] as String, + textAlign: TextAlign.center, style: TextStyle( color: isSelected ? Colors.blue : Colors.black87, fontSize: 12, @@ -70,24 +76,67 @@ Widget navigationCard( // 距离和耗时 Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric(horizontal: 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), - ) + IconButton( + icon: const Icon(Icons.turn_left, + color: Colors.blue, size: 20), + onPressed: () { + Get.toNamed(Routes.navigation); + }, + ), ], ), ), + + // 导航实时数据显示 + if (navigation != null) + Padding( + padding: const EdgeInsets.all(8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 圆形方向图标(如果有) + if (navigation["directionIcon"] != "") + Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: ClipOval( + child: Image.memory( + base64Decode(navigation["directionIcon"]), + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(width: 8), + // 文字信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (navigation["currentRoad"] != null) + Text('当前道路:${navigation["currentRoad"]}', + style: const TextStyle(fontSize: 12)), + if (navigation["nextRoad"] != null) + Text('下一道路:${navigation["nextRoad"]}', + style: const TextStyle(fontSize: 12)), + if (navigation["distanceText"] != null) + Text('剩余距离:${navigation["distanceText"]}', + style: const TextStyle(fontSize: 12)), + ], + ), + ), + ], + ), + ), ], ); }, 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/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt index 941d65cd5..a29867062 100644 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt @@ -1,10 +1,18 @@ package com.yunqiinnovation.amap_service +import android.app.Activity import android.content.Context import android.content.Intent +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.core.content.ContextCompat +import com.amap.api.navi.AMapNaviView +import com.amap.api.navi.AMapNaviViewListener +import com.amap.api.navi.model.NaviLatLng import com.yunqiinnovation.amap_service.NavigationServiceStarter import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -15,19 +23,21 @@ import org.json.JSONObject /** MusicServicePlugin */ -class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, NavigationListener { - private val TAG = "MusicServicePlugin" +class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, ActivityAware, NavigationListener { + private val TAG = "AmapServicePlugin" private lateinit var context: Context private lateinit var channel : MethodChannel + private var activity: Activity? = null; + private var aMapNaviView: AMapNaviView? = null private lateinit var eventChannel: EventChannel private var eventSink: EventChannel.EventSink? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service") + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.amap_service") channel.setMethodCallHandler(this) - eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service/events") + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.amap_service/events") eventChannel.setStreamHandler(this) // 添加监听器 NavigationServiceStarter.addListener(this) @@ -80,12 +90,93 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHan } override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - TODO("Not yet implemented") + // TODO("Not yet implemented") + this.eventSink = events } override fun onCancel(arguments: Any?) { - TODO("Not yet implemented") + // TODO("Not yet implemented") + this.eventSink = null + } + private fun startNaviWithUI(lat: Double, lon: Double, name: String) { + val act = activity ?: return + // 起点坐标,这里写死了,可以改成你定位到的当前位置 + val startPoint = NaviLatLng(39.909187, 116.397451) + val endPoint = NaviLatLng(lat, lon) + + val startList = mutableListOf().apply { add(startPoint) } + val endList = mutableListOf().apply { add(endPoint) } + + // 创建导航视图 + aMapNaviView = AMapNaviView(act).apply { + setAMapNaviViewListener(object : AMapNaviViewListener { + override fun onNaviSetting() { + // TODO: 处理设置事件 + } + override fun onNaviCancel() { + act.finish() + } + override fun onNaviBackClick(): Boolean { + act.finish() + return true // 返回true表示事件被消费 + } + override fun onNaviMapMode(p0: Int) { + // TODO + } + override fun onNaviTurnClick() { + // TODO + } + override fun onNextRoadClick() { + // TODO + } + override fun onScanViewButtonClick() { + // TODO + } + override fun onLockMap(p0: Boolean) { + // TODO: 锁定地图时的处理,比如什么也不做 + } + + 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") + } + }) + onCreate(null) + } + + // 设置导航页面为当前 Activity 的内容视图 + act.setContentView(aMapNaviView, FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )) + + // 启动导航(实时导航) +// aMapNavi.startNavi(NaviType.GPS) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onDetachedFromActivity() { + activity = null + } } 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 2fd8947f9..9096b8b10 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,10 +5,15 @@ import android.app.NotificationManager import android.app.Service import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.os.Build import android.os.IBinder +import android.util.Base64 import android.util.Log +import android.widget.RemoteViews 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 @@ -26,6 +31,7 @@ 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 @@ -61,6 +67,12 @@ class NavigationService: Service() { when (command) { "start" -> { try { + if (::aMapNavi.isInitialized) { + Log.w(TAG, "已有导航进行中,先停止当前导航") + aMapNavi.stopNavi() + aMapNavi.removeAMapNaviListener(naviListener) + } + startpos = intent.getStringExtra("startpos").toString() endpos = intent.getStringExtra("endpos").toString() naviType = intent.getStringExtra("naviType"); @@ -76,7 +88,7 @@ class NavigationService: Service() { } } "stop" -> { - aMapNavi.stopNavi(); + stopNavigation(); } } return START_STICKY @@ -107,6 +119,38 @@ class NavigationService: Service() { startForeground(NOTIFICATION_ID, notification) } + 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) + + if (iconBitmap != null) { + val tinted = tintBitmap(iconBitmap, ContextCompat.getColor(this, R.color.teal_200)) + builder.setLargeIcon(tinted) + } + + 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) + } // 实现监听器,处理导航播报等 private val naviListener = object : AMapNaviListener { override fun onInitNaviFailure() { @@ -162,12 +206,15 @@ class NavigationService: Service() { } override fun onEndEmulatorNavi() { - Log.e(TAG, "onEndEmulatorNavi! ") +// Log.e(TAG, "onEndEmulatorNavi! ") + Log.e(TAG, "onEndEmulatorNavi!") + stopNavigation() // TODO("Not yet implemented") } override fun onArriveDestination() { - Log.e(TAG, "onArriveDestination! ") + Log.e(TAG, "onArriveDestination!") + stopNavigation() // TODO("Not yet implemented") } @@ -200,9 +247,38 @@ class NavigationService: Service() { Log.e(TAG, "onGpsOpenStatus!") // TODO("Not yet implemented") } + private var lastNotificationUpdateTime = 0L + private val notificationUpdateInterval = 1000L // 1秒 + override fun onNaviInfoUpdate(naviInfo: NaviInfo?) { + if (naviInfo == null) return - override fun onNaviInfoUpdate(p0: NaviInfo?) { - Log.e(TAG, "onNaviInfoUpdate! $p0") + 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 ?: "") + ) + NavigationServiceStarter.sendEvent("amap_update",naviInfoMap) + updateNotification(contentText, directionBitmap) + Log.e(TAG, "onNaviInfoUpdate! $contentText") +// Log.e(TAG, "onNaviInfoUpdate! $p0") // TODO("Not yet implemented") } @@ -263,7 +339,7 @@ class NavigationService: Service() { override fun onCalculateRouteSuccess(ids: IntArray?) { Log.e(TAG, "onCalculateRouteSuccess! $ids") // AMapNavi.getInstance(this).startNavi(1); - aMapNavi.startNavi(1) + aMapNavi.startNavi(2) } override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { @@ -312,5 +388,14 @@ class NavigationService: Service() { } } + private fun stopNavigation() { + if (::aMapNavi.isInitialized) { + aMapNavi.stopNavi() + aMapNavi.removeAMapNaviListener(naviListener) + } + isRunning.set(false) + stopSelf() + } + override fun onBind(intent: Intent?): IBinder? = null } \ 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/values/colors.xml b/local_plugins/amap_service/android/src/main/res/values/colors.xml new file mode 100644 index 000000000..3c6f394b8 --- /dev/null +++ b/local_plugins/amap_service/android/src/main/res/values/colors.xml @@ -0,0 +1,15 @@ + + + + #6200EE + #3700B3 + #03DAC5 + #018786 + #000000 + #FFFFFF + + + #F44336 + #4CAF50 + #2196F3 + diff --git a/local_plugins/amap_service/lib/amap_service.dart b/local_plugins/amap_service/lib/amap_service.dart index f3b8e1122..703030e3f 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,58 @@ 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', ); - - // 初始化事件流 - _dataStream = _dataEventChannel.receiveBroadcastStream(); - _statusStream = _statusEventChannel.receiveBroadcastStream(); } - // 初始化SDK - Future initSdk({String? iosKey, String? androidKey}) async {} - // 定位初始化 - Future initLocation() async {} + /// 获取事件流 + Stream get events { + _eventStream ??= _eventChannel.receiveBroadcastStream().map((event) { + try { + final Map eventMap = jsonDecode(event); + final String eventName = eventMap['event']; + final Map eventData = eventMap['data']; + + // 将事件名称转换为枚举 + final AmapServiceEventType type = _stringToEventType(eventName); - // 开始定位 - Future startLocation() async {} + return AmapServiceEvent(type: type, data: eventData); + } catch (e) { + return AmapServiceEvent( + type: AmapServiceEventType.unknown, + data: {'error': e.toString(), 'rawEvent': event}, + ); + } + }); - // 监听定位返回位置 - Stream locationStream() { - return _dataStream; + return _eventStream!; } - // 停止定位 - Future stopLocation() 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 show() async { + await _methodChannel.invokeMethod('show'); + } // 销毁 Future dispose() async {}