Browse Source

上传导航代码

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
553e955f13
  1. 3
      lib/core/bindings/initial_binding.dart
  2. 8
      lib/data/services/navigation_manager.dart
  3. 92
      lib/modules/agent/controllers/agent_controller.dart
  4. 6
      lib/modules/agent/views/message_bubble.dart
  5. 71
      lib/modules/agent/views/message_bubble_navigation.dart
  6. 9
      lib/modules/navigation/bindings/navigation_binding.dart
  7. 5
      lib/modules/navigation/controllers/navigation_controller.dart
  8. 17
      lib/modules/navigation/views/navigation_view.dart
  9. 8
      lib/routes/app_pages.dart
  10. 1
      lib/routes/app_routes.dart
  11. 103
      local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt
  12. 97
      local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt
  13. 8
      local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml
  14. 15
      local_plugins/amap_service/android/src/main/res/values/colors.xml
  15. 107
      local_plugins/amap_service/lib/amap_service.dart

3
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/music_manager.dart';
import 'package:deep_voice/data/services/navigation_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../data/services/speech_factory.dart'; import '../../data/services/speech_factory.dart';
import '../../data/services/chat_history_service.dart'; import '../../data/services/chat_history_service.dart';
@ -35,6 +36,8 @@ class InitialBinding extends Bindings {
Get.lazyPut<BleManager>(() => BleManager(), fenix: true); Get.lazyPut<BleManager>(() => BleManager(), fenix: true);
// MusicManager // MusicManager
Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true); Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true);
// NavigationManager
Get.lazyPut<NavigationManager>(() => NavigationManager(), fenix: true);
// 火山AI服务 // 火山AI服务
// Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true); // Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);

8
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:deep_voice/core/utils/logger.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -7,7 +7,7 @@ class NavigationManager extends GetxService {
static NavigationManager get to => Get.find<NavigationManager>(); static NavigationManager get to => Get.find<NavigationManager>();
// 音乐服务实例 // 音乐服务实例
// final AmapService _amapervice = AmapService(); final AmapService _amapervice = AmapService();
/// 初始化 /// 初始化
@override @override
@ -22,6 +22,10 @@ class NavigationManager extends GetxService {
super.onClose(); super.onClose();
} }
Stream<AmapServiceEvent> events() {
return _amapervice.events;
}
//导航 //导航
Future<void> navigation(String org, String dis) async { Future<void> navigation(String org, String dis) async {
// await _amapervice.navigation(org, dis); // await _amapervice.navigation(org, dis);

92
lib/modules/agent/controllers/agent_controller.dart

@ -1,7 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; 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/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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:agent_service/agent_service.dart'; import 'package:agent_service/agent_service.dart';
@ -45,6 +48,7 @@ class AgentController extends GetxController {
//音乐播放管理 //音乐播放管理
final _musiceManager = Get.find<MusicManager>(); final _musiceManager = Get.find<MusicManager>();
final _navigationManager = Get.find<NavigationManager>();
var isPlaying = false.obs; var isPlaying = false.obs;
var currentUrl = "".obs; var currentUrl = "".obs;
@ -72,6 +76,9 @@ class AgentController extends GetxController {
// 流事件订阅 // 流事件订阅
StreamSubscription<AgentServiceEvent>? _eventSubscription; StreamSubscription<AgentServiceEvent>? _eventSubscription;
// 导航流事件订阅
StreamSubscription<AmapServiceEvent>? _amapeventSubscription;
// 用于跟踪AI回复状态 // 用于跟踪AI回复状态
bool _isNewAssistantResponse = true; bool _isNewAssistantResponse = true;
String _currentResponseId = ''; String _currentResponseId = '';
@ -395,12 +402,12 @@ class AgentController extends GetxController {
Logger.e(TAG, '解析meta数据失败: $e'); Logger.e(TAG, '解析meta数据失败: $e');
} }
} }
if(metaResult.isNotEmpty){ if (metaResult.isNotEmpty) {
// 判断是否为新的回复或响应ID是否改变 // 判断是否为新的回复或响应ID是否改变
if (_isNewAssistantResponse || _currentResponseId != responseId) { if (_isNewAssistantResponse || _currentResponseId != responseId) {
// 添加新消息 // 添加新消息
messages.add(Message(isUser: false, text: "", meta: metaResult)); messages
.add(Message(isUser: false, text: "", meta: metaResult));
_isNewAssistantResponse = false; _isNewAssistantResponse = false;
_currentResponseId = responseId; _currentResponseId = responseId;
} else { } else {
@ -422,6 +429,58 @@ class AgentController extends GetxController {
break; 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<String, dynamic>.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 { try {
// 创建待请求权限列表 // 创建待请求权限列表
List<Permission> permissionsToRequest = []; List<Permission> permissionsToRequest = [];
if (Platform.isAndroid) { if (Platform.isAndroid) {
// Android 需要考虑SDK版本不同的权限要求 // Android 需要考虑SDK版本不同的权限要求
permissionsToRequest.add(Permission.microphone); permissionsToRequest.add(Permission.microphone);
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
// iOS 只需要麦克风权限 // iOS 只需要麦克风权限
permissionsToRequest.add(Permission.microphone); permissionsToRequest.add(Permission.microphone);
} }
// 批量请求所有需要的权限 // 批量请求所有需要的权限
final statuses = await permissionsToRequest.request(); final statuses = await permissionsToRequest.request();
// 检查是否所有权限都已授予 // 检查是否所有权限都已授予
bool allGranted = true; bool allGranted = true;
Permission? deniedPermission; Permission? deniedPermission;
statuses.forEach((permission, status) { statuses.forEach((permission, status) {
if (!status.isGranted) { if (!status.isGranted) {
allGranted = false; allGranted = false;
@ -560,33 +617,33 @@ class AgentController extends GetxController {
Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status'); Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
} }
}); });
// 单独检查麦克风权限状态,因为这是主要需求 // 单独检查麦克风权限状态,因为这是主要需求
final micStatus = await Permission.microphone.status; final micStatus = await Permission.microphone.status;
// 如果所有权限都已授予,或至少麦克风权限已授予,返回true // 如果所有权限都已授予,或至少麦克风权限已授予,返回true
if (allGranted || micStatus.isGranted) { if (allGranted || micStatus.isGranted) {
return true; return true;
} }
// 处理特定权限被永久拒绝的情况 // 处理特定权限被永久拒绝的情况
if (micStatus.isPermanentlyDenied) { if (micStatus.isPermanentlyDenied) {
_showPermissionDialog(true); _showPermissionDialog(true);
return false; return false;
} }
// 普通拒绝情况 // 普通拒绝情况
if (deniedPermission != null) { if (deniedPermission != null) {
_showPermissionDialog(false); _showPermissionDialog(false);
} }
return false; return false;
} catch (e) { } catch (e) {
Logger.e(TAG, '检查麦克风权限失败: $e'); Logger.e(TAG, '检查麦克风权限失败: $e');
return false; return false;
} }
} }
// 获取Android SDK版本 // 获取Android SDK版本
Future<int> _getAndroidSdkVersion() async { Future<int> _getAndroidSdkVersion() async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
@ -790,4 +847,9 @@ class AgentController extends GetxController {
AgentService.processTextInput(messages[index].text, AgentService.processTextInput(messages[index].text,
speakResponse: isTtsEnabled.value); speakResponse: isTtsEnabled.value);
} }
//去导航
void gotoNavigation() {
Get.toNamed(Routes.navigation);
}
} }

6
lib/modules/agent/views/message_bubble.dart

@ -285,8 +285,12 @@ class MessageBubble extends GetView<AgentController> {
} else if (meta!.keys.contains('card_navigation')) { } else if (meta!.keys.contains('card_navigation')) {
final rawList = meta!['card_navigation']['items'] as List; final rawList = meta!['card_navigation']['items'] as List;
final list = rawList.map((e) => Map<String, dynamic>.from(e)).toList(); final list = rawList.map((e) => Map<String, dynamic>.from(e)).toList();
final rawNavigation = meta!['card_navigation']['navigation'];
final Map<String, dynamic>? navigation =
rawNavigation is Map<String, dynamic> ? rawNavigation : null;
print("导航实时数据 ${rawNavigation}");
// 导航 // 导航
return navigationCard(context, modes: list); return navigationCard(context, modes: list, navigation: navigation);
} }
// if (message.contains('播放列表') || message.contains('音乐')) { // if (message.contains('播放列表') || message.contains('音乐')) {

71
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:flutter/material.dart';
import 'package:get/get.dart';
/// A widget that displays a navigation card with different modes of transportation. /// A widget that displays a navigation card with different modes of transportation.
Widget navigationCard( Widget navigationCard(
BuildContext context, { BuildContext context, {
required List<Map<String, dynamic>> modes, required List<Map<String, dynamic>> modes,
double height = 260, Map<String, dynamic>? navigation,
double height = 300,
}) { }) {
int currentIndex = 0; int currentIndex = 0;
@ -24,6 +28,7 @@ Widget navigationCard(
child: StatefulBuilder( child: StatefulBuilder(
builder: (context, setInnerState) { builder: (context, setInnerState) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 路线图片 // 路线图片
ClipRRect( ClipRRect(
@ -56,6 +61,7 @@ Widget navigationCard(
width: 50, width: 50,
child: Text( child: Text(
modes[index]["ntype"] as String, modes[index]["ntype"] as String,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: isSelected ? Colors.blue : Colors.black87, color: isSelected ? Colors.blue : Colors.black87,
fontSize: 12, fontSize: 12,
@ -70,24 +76,67 @@ Widget navigationCard(
// 距离和耗时 // 距离和耗时
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('距离:${modes[currentIndex]["distance"]}', Text('距离:${modes[currentIndex]["distance"]}',
style: const TextStyle(fontSize: 12, color: Colors.blue)), style: const TextStyle(fontSize: 12, color: Colors.blue)),
Container( IconButton(
decoration: BoxDecoration( icon: const Icon(Icons.turn_left,
shape: BoxShape.circle, // 👈 圆形背景 color: Colors.blue, size: 20),
color: Colors.blue, onPressed: () {
), Get.toNamed(Routes.navigation);
child: Icon(Icons.turn_left, },
color: Colors.white, // 👈 圆形图标 ),
size: 20),
)
], ],
), ),
), ),
// 导航实时数据显示
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)),
],
),
),
],
),
),
], ],
); );
}, },

9
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>(() => NavigationController());
}
}

5
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(); //单例访问模式
}

17
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<NavigationController> {
const NavigationView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('地图展示')),
body: Stack(
children: [],
),
);
}
}

8
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 'package:get/get.dart';
import '../modules/feedback/bindings/feedback_bindings.dart'; import '../modules/feedback/bindings/feedback_bindings.dart';
import '../modules/feedback/views/feedback_view.dart'; import '../modules/feedback/views/feedback_view.dart';
@ -189,5 +190,12 @@ abstract class AppPages {
transition: Transition.rightToLeft, transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250), transitionDuration: Duration(milliseconds: 250),
), ),
GetPage(
name: Routes.navigation,
page: () => const NavigationView(),
binding: AgentBinding(),
transition: Transition.rightToLeft,
transitionDuration: Duration(milliseconds: 250),
),
]; ];
} }

1
lib/routes/app_routes.dart

@ -44,4 +44,5 @@ abstract class Routes {
// 支持服务 // 支持服务
static const feedback = '/feedback'; // 用户反馈 static const feedback = '/feedback'; // 用户反馈
static const agent = '/agent'; // 在线客服 static const agent = '/agent'; // 在线客服
static const navigation = '/navigation'; // 实时导航
} }

103
local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt

@ -1,10 +1,18 @@
package com.yunqiinnovation.amap_service package com.yunqiinnovation.amap_service
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.content.ContextCompat 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 com.yunqiinnovation.amap_service.NavigationServiceStarter
import io.flutter.embedding.engine.plugins.FlutterPlugin 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.EventChannel
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@ -15,19 +23,21 @@ import org.json.JSONObject
/** MusicServicePlugin */ /** MusicServicePlugin */
class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, NavigationListener { class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, ActivityAware, NavigationListener {
private val TAG = "MusicServicePlugin" private val TAG = "AmapServicePlugin"
private lateinit var context: Context private lateinit var context: Context
private lateinit var channel : MethodChannel private lateinit var channel : MethodChannel
private var activity: Activity? = null;
private var aMapNaviView: AMapNaviView? = null
private lateinit var eventChannel: EventChannel private lateinit var eventChannel: EventChannel
private var eventSink: EventChannel.EventSink? = null private var eventSink: EventChannel.EventSink? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.amap_service")
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service/events") eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.amap_service/events")
eventChannel.setStreamHandler(this) eventChannel.setStreamHandler(this)
// 添加监听器 // 添加监听器
NavigationServiceStarter.addListener(this) NavigationServiceStarter.addListener(this)
@ -80,12 +90,93 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHan
} }
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
TODO("Not yet implemented") // TODO("Not yet implemented")
this.eventSink = events
} }
override fun onCancel(arguments: Any?) { 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<NaviLatLng>().apply { add(startPoint) }
val endList = mutableListOf<NaviLatLng>().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
}
} }

97
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.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Base64
import android.util.Log import android.util.Log
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.amap.api.navi.AMapNavi import com.amap.api.navi.AMapNavi
import com.amap.api.navi.AMapNaviListener import com.amap.api.navi.AMapNaviListener
import com.amap.api.navi.enums.NaviType 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.AimLessModeStat
import com.amap.api.navi.model.NaviInfo import com.amap.api.navi.model.NaviInfo
import com.amap.api.navi.model.NaviLatLng import com.amap.api.navi.model.NaviLatLng
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -61,6 +67,12 @@ class NavigationService: Service() {
when (command) { when (command) {
"start" -> { "start" -> {
try { try {
if (::aMapNavi.isInitialized) {
Log.w(TAG, "已有导航进行中,先停止当前导航")
aMapNavi.stopNavi()
aMapNavi.removeAMapNaviListener(naviListener)
}
startpos = intent.getStringExtra("startpos").toString() startpos = intent.getStringExtra("startpos").toString()
endpos = intent.getStringExtra("endpos").toString() endpos = intent.getStringExtra("endpos").toString()
naviType = intent.getStringExtra("naviType"); naviType = intent.getStringExtra("naviType");
@ -76,7 +88,7 @@ class NavigationService: Service() {
} }
} }
"stop" -> { "stop" -> {
aMapNavi.stopNavi(); stopNavigation();
} }
} }
return START_STICKY return START_STICKY
@ -107,6 +119,38 @@ class NavigationService: Service() {
startForeground(NOTIFICATION_ID, notification) 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 { private val naviListener = object : AMapNaviListener {
override fun onInitNaviFailure() { override fun onInitNaviFailure() {
@ -162,12 +206,15 @@ class NavigationService: Service() {
} }
override fun onEndEmulatorNavi() { override fun onEndEmulatorNavi() {
Log.e(TAG, "onEndEmulatorNavi! ") // Log.e(TAG, "onEndEmulatorNavi! ")
Log.e(TAG, "onEndEmulatorNavi!")
stopNavigation()
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
override fun onArriveDestination() { override fun onArriveDestination() {
Log.e(TAG, "onArriveDestination! ") Log.e(TAG, "onArriveDestination!")
stopNavigation()
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
@ -200,9 +247,38 @@ class NavigationService: Service() {
Log.e(TAG, "onGpsOpenStatus!") Log.e(TAG, "onGpsOpenStatus!")
// TODO("Not yet implemented") // 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?) { val currentTime = System.currentTimeMillis()
Log.e(TAG, "onNaviInfoUpdate! $p0") 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<String, Any> = 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") // TODO("Not yet implemented")
} }
@ -263,7 +339,7 @@ class NavigationService: Service() {
override fun onCalculateRouteSuccess(ids: IntArray?) { override fun onCalculateRouteSuccess(ids: IntArray?) {
Log.e(TAG, "onCalculateRouteSuccess! $ids") Log.e(TAG, "onCalculateRouteSuccess! $ids")
// AMapNavi.getInstance(this).startNavi(1); // AMapNavi.getInstance(this).startNavi(1);
aMapNavi.startNavi(1) aMapNavi.startNavi(2)
} }
override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { 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 override fun onBind(intent: Intent?): IBinder? = null
} }

8
local_plugins/amap_service/android/src/main/res/drawable/ic_navigation.xml

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24.0"
android:viewportHeight="24"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#000000"
android:pathData="M12,2L3,21h9v-6h6v6h3L12,2z" /> 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"/>
</vector> </vector>

15
local_plugins/amap_service/android/src/main/res/values/colors.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 主色调 -->
<color name="purple_500">#6200EE</color>
<color name="purple_700">#3700B3</color>
<color name="teal_200">#03DAC5</color>
<color name="teal_700">#018786</color>
<color name="black">#000000</color>
<color name="white">#FFFFFF</color>
<!-- 额外颜色 -->
<color name="red_500">#F44336</color>
<color name="green_500">#4CAF50</color>
<color name="blue_500">#2196F3</color>
</resources>

107
local_plugins/amap_service/lib/amap_service.dart

@ -1,5 +1,38 @@
import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
/// 代理服务事件类型
enum AmapServiceEventType {
/// 导航启动
starte,
///导航结束
stope,
update,
/// 错误
error,
/// 未知事件
unknown,
}
/// 代理服务事件数据
class AmapServiceEvent {
/// 事件类型
final AmapServiceEventType type;
/// 事件数据
final Map<String, dynamic> data;
AmapServiceEvent({required this.type, required this.data});
@override
String toString() => 'AmapServiceEvent(type: $type, data: $data)';
}
class AmapService { class AmapService {
static final AmapService _instance = AmapService._internal(); static final AmapService _instance = AmapService._internal();
@ -12,48 +45,58 @@ class AmapService {
); );
// 事件通道 - 简化为只使用数据和状态通道 // 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel; late final EventChannel _eventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
static Stream<AmapServiceEvent>? _eventStream;
// 内部构造函数 // 内部构造函数
AmapService._internal() { AmapService._internal() {
_dataEventChannel = const EventChannel( _eventChannel = const EventChannel(
'com.yunqiinnovation.amap_service/data_events', 'com.yunqiinnovation.amap_service/events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.amap_service/status_events',
); );
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
} }
// 初始化SDK
Future<void> initSdk({String? iosKey, String? androidKey}) async {}
// 定位初始化 /// 获取事件流
Future<void> initLocation() async {} Stream<AmapServiceEvent> get events {
_eventStream ??= _eventChannel.receiveBroadcastStream().map((event) {
try {
final Map<String, dynamic> eventMap = jsonDecode(event);
final String eventName = eventMap['event'];
final Map<String, dynamic> eventData = eventMap['data'];
// 将事件名称转换为枚举
final AmapServiceEventType type = _stringToEventType(eventName);
// 开始定位 return AmapServiceEvent(type: type, data: eventData);
Future<void> startLocation() async {} } catch (e) {
return AmapServiceEvent(
type: AmapServiceEventType.unknown,
data: {'error': e.toString(), 'rawEvent': event},
);
}
});
// 监听定位返回位置 return _eventStream!;
Stream<dynamic> locationStream() {
return _dataStream;
} }
// 停止定位 /// 将字符串事件名转换为枚举类型
Future<void> 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<void> show() async {
await _methodChannel.invokeMethod('show');
}
// 销毁 // 销毁
Future<void> dispose() async {} Future<void> dispose() async {}

Loading…
Cancel
Save