From da29b60da377d0bc4a456b0386bb7735fbb23a22 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 20 May 2025 09:54:08 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentService.kt | 1 - .../amap_service/android/build.gradle | 3 + .../amap_service/AmapServicePlugin.kt | 41 +- .../amap_service/NavigationService.kt | 2 + .../amap_service/NavigationServiceStarter.kt | 72 +++ .../amap_service/lib/amap_controller.dart | 174 +++++++ .../amap_service/lib/amap_service_map.dart | 20 + .../amap_service/lib/amap_widget.dart | 425 ++++++++++++++++++ .../lib/core/amap_flutter_platform.dart | 41 ++ .../amap_service/lib/core/map_event.dart | 77 ++++ .../core/method_channel_amap_flutter_map.dart | 274 +++++++++++ .../amap_service/lib/types/base_overlay.dart | 29 ++ .../amap_service/lib/types/bitmap.dart | 140 ++++++ .../amap_service/lib/types/camera.dart | 150 +++++++ .../amap_service/lib/types/marker.dart | 249 ++++++++++ .../lib/types/marker_updates.dart | 105 +++++ .../amap_service/lib/types/polygon.dart | 120 +++++ .../lib/types/polygon_updates.dart | 108 +++++ .../amap_service/lib/types/polyline.dart | 198 ++++++++ .../lib/types/polyline_updates.dart | 108 +++++ .../amap_service/lib/types/types.dart | 11 + local_plugins/amap_service/lib/types/ui.dart | 257 +++++++++++ local_plugins/amap_service/pubspec.yaml | 4 + .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/cache.properties | 4 +- 25 files changed, 2608 insertions(+), 5 deletions(-) create mode 100644 local_plugins/amap_service/lib/amap_controller.dart create mode 100644 local_plugins/amap_service/lib/amap_service_map.dart create mode 100644 local_plugins/amap_service/lib/amap_widget.dart create mode 100644 local_plugins/amap_service/lib/core/amap_flutter_platform.dart create mode 100644 local_plugins/amap_service/lib/core/map_event.dart create mode 100644 local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart create mode 100644 local_plugins/amap_service/lib/types/base_overlay.dart create mode 100644 local_plugins/amap_service/lib/types/bitmap.dart create mode 100644 local_plugins/amap_service/lib/types/camera.dart create mode 100644 local_plugins/amap_service/lib/types/marker.dart create mode 100644 local_plugins/amap_service/lib/types/marker_updates.dart create mode 100644 local_plugins/amap_service/lib/types/polygon.dart create mode 100644 local_plugins/amap_service/lib/types/polygon_updates.dart create mode 100644 local_plugins/amap_service/lib/types/polyline.dart create mode 100644 local_plugins/amap_service/lib/types/polyline_updates.dart create mode 100644 local_plugins/amap_service/lib/types/types.dart create mode 100644 local_plugins/amap_service/lib/types/ui.dart 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 41c2aa990..f95b4a559 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 @@ -1106,7 +1106,6 @@ object AgentService : CoroutineScope { /** * 处理导航 * 将ai返回的音乐连接 在后台启动播放 - * * @param musicUrl 图片文件路径 * @param name 歌曲名称,默认为空 * @param singer 歌手,默认为false diff --git a/local_plugins/amap_service/android/build.gradle b/local_plugins/amap_service/android/build.gradle index 4bd0a8a1d..d9568dd2d 100644 --- a/local_plugins/amap_service/android/build.gradle +++ b/local_plugins/amap_service/android/build.gradle @@ -65,3 +65,6 @@ android { } } } +dependencies { + implementation(project(":deepvoice_utils")) +} 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 020d98a61..941d65cd5 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 @@ -3,24 +3,34 @@ package com.yunqiinnovation.amap_service import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat -// import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.amap_service.NavigationServiceStarter import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import org.json.JSONObject /** MusicServicePlugin */ -class AmapServicePlugin: FlutterPlugin, MethodCallHandler { +class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, NavigationListener { private val TAG = "MusicServicePlugin" private lateinit var context: Context private lateinit var channel : MethodChannel + 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.setMethodCallHandler(this) + + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service/events") + eventChannel.setStreamHandler(this) + // 添加监听器 + NavigationServiceStarter.addListener(this) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { @@ -51,4 +61,31 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler { } 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") + } + + override fun onCancel(arguments: Any?) { + 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..093e14137 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 @@ -67,6 +67,7 @@ class NavigationService: Service() { Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") // 初始化导航 aMapNavi = AMapNavi.getInstance(applicationContext) + aMapNavi.setUseInnerVoice(true) aMapNavi.setBroadcastMode(1) aMapNavi.addAMapNaviListener(naviListener) @@ -261,6 +262,7 @@ class NavigationService: Service() { override fun onCalculateRouteSuccess(ids: IntArray?) { Log.e(TAG, "onCalculateRouteSuccess! $ids") + AMapNavi.getInstance(this).startNavi(NaviType.GPS); aMapNavi.startNavi(NaviType.EMULATOR) } 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 index 788495e9b..980edb166 100644 --- 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 @@ -2,11 +2,29 @@ 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 com.yunqiinnovation.amap_service.NavigationService.Companion +import java.util.Collections + +/** + * 代理服务事件监听接口 + */ +interface NavigationListener { + /** + * 当有事件发生时调用此方法 + * + * @param eventName 事件名称 + * @param data 事件数据 + */ + fun onEvent(eventName: String, data: Map) +} object NavigationServiceStarter { private val TAG = "NavigationServiceStarter" + private val listeners = Collections.synchronizedSet(mutableSetOf()) //播放单曲 fun startServiceWithCommand(context: Context, command: String, startpos: String? = null, endpos: String? = null, ntype: String? = null) { Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $startpos $endpos $ntype") @@ -31,4 +49,58 @@ object NavigationServiceStarter { } Log.d(TAG, "liwei------------------- Service start success: $success") } + + + /** + * 添加事件监听器 + * @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/lib/amap_controller.dart b/local_plugins/amap_service/lib/amap_controller.dart new file mode 100644 index 000000000..48db2802d --- /dev/null +++ b/local_plugins/amap_service/lib/amap_controller.dart @@ -0,0 +1,174 @@ +part of amap_service_map; + +final MethodChannelAMapFlutterMap _methodChannel = + AMapFlutterPlatform.instance as MethodChannelAMapFlutterMap; + +/// 地图通信中心 +class AMapController { + final int mapId; + final _MapState _mapState; + + AMapController._( + CameraPosition initCameraPosition, + this._mapState, { + required this.mapId, + }) { + _connectStreams(mapId); + } + + ///根据传入的id初始化[AMapController] + /// 主要用于在[AMapWidget]初始化时在[AMapWidget.onMapCreated]中初始化controller + static Future init( + int id, + CameraPosition initialCameration, + _MapState mapState, + ) async { + await _methodChannel.init(id); + return AMapController._(initialCameration, mapState, mapId: id); + } + + ///只用于测试 + ///用于与native的通信 + @visibleForTesting + MethodChannel get channel { + return _methodChannel.channel(mapId); + } + + void _connectStreams(int mapId) { + if (_mapState.widget.onLocationChanged != null) { + _methodChannel + .onLocationChanged(mapId: mapId) + .listen( + (LocationChangedEvent e) => + _mapState.widget.onLocationChanged!(e.value), + ); + } + + if (_mapState.widget.onCameraMove != null) { + _methodChannel + .onCameraMove(mapId: mapId) + .listen( + (CameraPositionMoveEvent e) => + _mapState.widget.onCameraMove!(e.value), + ); + } + if (_mapState.widget.onCameraMoveEnd != null) { + _methodChannel + .onCameraMoveEnd(mapId: mapId) + .listen( + (CameraPositionMoveEndEvent e) => + _mapState.widget.onCameraMoveEnd!(e.value), + ); + } + if (_mapState.widget.onTap != null) { + _methodChannel + .onMapTap(mapId: mapId) + .listen(((MapTapEvent e) => _mapState.widget.onTap!(e.value))); + } + if (_mapState.widget.onLongPress != null) { + _methodChannel + .onMapLongPress(mapId: mapId) + .listen( + ((MapLongPressEvent e) => _mapState.widget.onLongPress!(e.value)), + ); + } + + if (_mapState.widget.onPoiTouched != null) { + _methodChannel + .onPoiTouched(mapId: mapId) + .listen( + ((MapPoiTouchEvent e) => _mapState.widget.onPoiTouched!(e.value)), + ); + } + + _methodChannel + .onMarkerTap(mapId: mapId) + .listen((MarkerTapEvent e) => _mapState.onMarkerTap(e.value)); + + _methodChannel + .onMarkerDragEnd(mapId: mapId) + .listen( + (MarkerDragEndEvent e) => + _mapState.onMarkerDragEnd(e.value, e.position), + ); + + _methodChannel + .onPolylineTap(mapId: mapId) + .listen((PolylineTapEvent e) => _mapState.onPolylineTap(e.value)); + } + + void disponse() { + _methodChannel.dispose(id: mapId); + } + + Future _updateMapOptions(Map optionsUpdate) { + return _methodChannel.updateMapOptions(optionsUpdate, mapId: mapId); + } + + Future _updateMarkers(MarkerUpdates markerUpdates) { + return _methodChannel.updateMarkers(markerUpdates, mapId: mapId); + } + + Future _updatePolylines(PolylineUpdates polylineUpdates) { + return _methodChannel.updatePolylines(polylineUpdates, mapId: mapId); + } + + Future _updatePolygons(PolygonUpdates polygonUpdates) { + return _methodChannel.updatePolygons(polygonUpdates, mapId: mapId); + } + + ///改变地图视角 + /// + ///通过[CameraUpdate]对象设置新的中心点、缩放比例、放大缩小、显示区域等内容 + /// + ///(注意:iOS端设置显示区域时,不支持duration参数,动画时长使用iOS地图默认值350毫秒) + /// + ///可选属性[animated]用于控制是否执行动画移动 + /// + ///可选属性[duration]用于控制执行动画的时长,默认250毫秒,单位:毫秒 + Future moveCamera( + CameraUpdate cameraUpdate, { + bool animated = true, + int duration = 250, + }) { + return _methodChannel.moveCamera( + cameraUpdate, + mapId: mapId, + animated: animated, + duration: duration, + ); + } + + ///设置地图每秒渲染的帧数 + Future setRenderFps(int fps) { + return _methodChannel.setRenderFps(fps, mapId: mapId); + } + + ///地图截屏 + Future takeSnapshot() { + return _methodChannel.takeSnapshot(mapId: mapId); + } + + /// 获取地图审图号(普通地图) + /// + /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 + /// + /// 如高德地图在"关于"中体现 + Future getMapContentApprovalNumber() { + return _methodChannel.getMapContentApprovalNumber(mapId: mapId); + } + + /// 获取地图审图号(卫星地图) + /// + /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 + /// + /// 如高德地图在"关于"中体现 + Future getSatelliteImageApprovalNumber() { + return _methodChannel.getSatelliteImageApprovalNumber(mapId: mapId); + } + + /// 清空缓存 + Future clearDisk() { + return _methodChannel.clearDisk(mapId: mapId); + } +} diff --git a/local_plugins/amap_service/lib/amap_service_map.dart b/local_plugins/amap_service/lib/amap_service_map.dart new file mode 100644 index 000000000..d8404d6d7 --- /dev/null +++ b/local_plugins/amap_service/lib/amap_service_map.dart @@ -0,0 +1,20 @@ +/// 高德地图Flutter插件入口文件 +library amap_service_map; + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; +import 'package:amap_flutter_map/src/core/map_event.dart'; +import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; +import 'package:amap_flutter_map/src/types/types.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +export 'package:amap_flutter_map/src/types/types.dart'; + +part 'amap_controller.dart'; +part 'amap_widget.dart'; diff --git a/local_plugins/amap_service/lib/amap_widget.dart b/local_plugins/amap_service/lib/amap_widget.dart new file mode 100644 index 000000000..90c7b4407 --- /dev/null +++ b/local_plugins/amap_service/lib/amap_widget.dart @@ -0,0 +1,425 @@ +part of amap_service_map; + +typedef void MapCreatedCallback(AMapController controller); + +///用于展示高德地图的Widget +class AMapWidget extends StatefulWidget { + ///高德开放平台的key + /// + final AMapApiKey? apiKey; + + /// 初始化时的地图中心点 + final CameraPosition initialCameraPosition; + + ///地图类型 + final MapType mapType; + + ///自定义地图样式 + final CustomStyleOptions? customStyleOptions; + + ///定位小蓝点 + final MyLocationStyleOptions? myLocationStyleOptions; + + ///缩放级别范围 + final MinMaxZoomPreference? minMaxZoomPreference; + + ///地图显示范围 + final LatLngBounds? limitBounds; + + ///显示路况开关 + final bool trafficEnabled; + + /// 地图poi是否允许点击 + final bool touchPoiEnabled; + + ///是否显示3D建筑物 + final bool buildingsEnabled; + + ///是否显示底图文字标注 + final bool labelsEnabled; + + ///是否显示指南针 + final bool compassEnabled; + + ///是否显示比例尺 + final bool scaleEnabled; + + ///是否支持缩放手势 + final bool zoomGesturesEnabled; + + ///是否支持滑动手势 + final bool scrollGesturesEnabled; + + ///是否支持旋转手势 + final bool rotateGesturesEnabled; + + ///是否支持倾斜手势 + final bool tiltGesturesEnabled; + + /// 地图上显示的Marker + final Set markers; + + /// 地图上显示的polyline + final Set polylines; + + /// 地图上显示的polygon + final Set polygons; + + /// 地图创建成功的回调, 收到此回调之后才可以操作地图 + final MapCreatedCallback? onMapCreated; + + /// 相机视角持续移动的回调 + final ArgumentCallback? onCameraMove; + + /// 相机视角移动结束的回调 + final ArgumentCallback? onCameraMoveEnd; + + /// 地图单击事件的回调 + final ArgumentCallback? onTap; + + /// 地图长按事件的回调 + final ArgumentCallback? onLongPress; + + /// 地图POI的点击回调,需要`touchPoiEnabled`true,才能回调 + final ArgumentCallback? onPoiTouched; + + ///位置回调 + final ArgumentCallback? onLocationChanged; + + ///需要应用到地图上的手势集合 + final Set> gestureRecognizers; + + ///高德合规声明配置 + /// + /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy + final AMapPrivacyStatement? privacyStatement; + + /// 创建一个展示高德地图的widget + /// + /// 如果使用的高德地图SDK的版本是8.1.0及以上版本, + /// 在app首次启动时必须传入高德合规声明配置[privacyStatement],后续如果没有变化不需要重复设置 + ///
  • [privacyStatement.hasContains] 隐私权政策是否包含高德开平隐私权政策
  • + ///
  • [privacyStatement.hasShow] 是否已经弹窗展示给用户
  • + ///
  • [privacyStatement.hasAgree] 隐私权政策是否已经取得用户同意
  • + /// 以上三个值,任何一个为false都会造成地图插件不工作(白屏情况) + /// + /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy + /// + /// [AssertionError] will be thrown if [initialCameraPosition] is null; + const AMapWidget({ + Key? key, + this.privacyStatement, + this.apiKey, + this.initialCameraPosition = const CameraPosition( + target: LatLng(39.909187, 116.397451), + zoom: 10, + ), + this.mapType = MapType.normal, + this.buildingsEnabled = true, + this.compassEnabled = false, + this.labelsEnabled = true, + this.limitBounds, + this.minMaxZoomPreference, + this.rotateGesturesEnabled = true, + this.scaleEnabled = true, + this.scrollGesturesEnabled = true, + this.tiltGesturesEnabled = true, + this.touchPoiEnabled = true, + this.trafficEnabled = false, + this.zoomGesturesEnabled = true, + this.onMapCreated, + this.gestureRecognizers = const >{}, + this.customStyleOptions, + this.myLocationStyleOptions, + this.onCameraMove, + this.onCameraMoveEnd, + this.onLocationChanged, + this.onTap, + this.onLongPress, + this.onPoiTouched, + this.markers = const {}, + this.polylines = const {}, + this.polygons = const {}, + }) : super(key: key); + + /// + @override + State createState() => _MapState(); +} + +class _MapState extends State { + Map _markers = {}; + Map _polylines = {}; + Map _polygons = {}; + + final Completer _controller = Completer(); + late _AMapOptions _mapOptions; + + @override + Widget build(BuildContext context) { + AMapUtil.init(context); + final Map creationParams = { + 'privacyStatement': widget.privacyStatement?.toMap(), + 'apiKey': widget.apiKey?.toMap(), + 'initialCameraPosition': widget.initialCameraPosition.toMap(), + 'options': _mapOptions.toMap(), + 'markersToAdd': serializeOverlaySet(widget.markers), + 'polylinesToAdd': serializeOverlaySet(widget.polylines), + 'polygonsToAdd': serializeOverlaySet(widget.polygons), + }; + Widget mapView = _methodChannel.buildView( + creationParams, + widget.gestureRecognizers, + onPlatformViewCreated, + ); + return mapView; + } + + @override + void initState() { + super.initState(); + _mapOptions = _AMapOptions.fromWidget(widget); + _markers = keyByMarkerId(widget.markers); + _polygons = keyByPolygonId(widget.polygons); + _polylines = keyByPolylineId(widget.polylines); + print('initState AMapWidget'); + } + + @override + void dispose() async { + super.dispose(); + AMapController controller = await _controller.future; + controller.disponse(); + print('dispose AMapWidget with mapId: ${controller.mapId}'); + } + + @override + void reassemble() { + super.reassemble(); + print('reassemble AMapWidget'); + } + + @override + void deactivate() async { + super.deactivate(); + print('deactivate AMapWidget}'); + } + + @override + void didUpdateWidget(covariant AMapWidget oldWidget) { + super.didUpdateWidget(oldWidget); + _updateOptions(); + _updateMarkers(); + _updatePolylines(); + _updatePolygons(); + } + + Future onPlatformViewCreated(int id) async { + final AMapController controller = await AMapController.init( + id, + widget.initialCameraPosition, + this, + ); + _controller.complete(controller); + final MapCreatedCallback? _onMapCreated = widget.onMapCreated; + if (_onMapCreated != null) { + _onMapCreated(controller); + } + } + + void onMarkerTap(String markerId) { + final Marker? _marker = _markers[markerId]; + if (_marker != null) { + final ArgumentCallback? _onTap = _marker.onTap; + if (_onTap != null) { + _onTap(markerId); + } + } + } + + void onMarkerDragEnd(String markerId, LatLng position) { + final Marker? _marker = _markers[markerId]; + if (_marker != null) { + final MarkerDragEndCallback? _onDragEnd = _marker.onDragEnd; + if (_onDragEnd != null) { + _onDragEnd(markerId, position); + } + } + } + + void onPolylineTap(String polylineId) { + final Polyline? _polyline = _polylines[polylineId]; + if (_polyline != null) { + final ArgumentCallback? _onTap = _polyline.onTap; + if (_onTap != null) { + _onTap(polylineId); + } + } + } + + void _updateOptions() async { + final _AMapOptions newOptions = _AMapOptions.fromWidget(widget); + final Map updates = _mapOptions._updatesMap(newOptions); + if (updates.isEmpty) { + return; + } + final AMapController controller = await _controller.future; + // ignore: unawaited_futures + controller._updateMapOptions(updates); + _mapOptions = newOptions; + } + + void _updateMarkers() async { + final AMapController controller = await _controller.future; + // ignore: unawaited_futures + controller._updateMarkers( + MarkerUpdates.from(_markers.values.toSet(), widget.markers), + ); + _markers = keyByMarkerId(widget.markers); + } + + void _updatePolylines() async { + final AMapController controller = await _controller.future; + controller._updatePolylines( + PolylineUpdates.from(_polylines.values.toSet(), widget.polylines), + ); + _polylines = keyByPolylineId(widget.polylines); + } + + void _updatePolygons() async { + final AMapController controller = await _controller.future; + controller._updatePolygons( + PolygonUpdates.from(_polygons.values.toSet(), widget.polygons), + ); + _polygons = keyByPolygonId(widget.polygons); + } +} + +//高德地图参数设置 +class _AMapOptions { + ///地图类型 + final MapType mapType; + + ///自定义地图样式 + final CustomStyleOptions? customStyleOptions; + + ///定位小蓝点 + final MyLocationStyleOptions? myLocationStyleOptions; + + //缩放级别范围 + final MinMaxZoomPreference? minMaxZoomPreference; + + ///地图显示范围 + final LatLngBounds? limitBounds; + + ///显示路况开关 + final bool? trafficEnabled; + + /// 地图poi是否允许点击 + final bool? touchPoiEnabled; + + ///是否显示3D建筑物 + final bool? buildingsEnabled; + + ///是否显示底图文字标注 + final bool? labelsEnabled; + + ///是否显示指南针 + final bool? compassEnabled; + + ///是否显示比例尺 + final bool? scaleEnabled; + + ///是否支持缩放手势 + final bool? zoomGesturesEnabled; + + ///是否支持滑动手势 + final bool? scrollGesturesEnabled; + + ///是否支持旋转手势 + final bool? rotateGesturesEnabled; + + ///是否支持仰角手势 + final bool? tiltGesturesEnabled; + + _AMapOptions({ + this.mapType = MapType.normal, + this.buildingsEnabled, + this.customStyleOptions, + this.myLocationStyleOptions, + this.compassEnabled, + this.labelsEnabled, + this.limitBounds, + this.minMaxZoomPreference, + this.scaleEnabled, + this.touchPoiEnabled, + this.trafficEnabled, + this.rotateGesturesEnabled, + this.scrollGesturesEnabled, + this.tiltGesturesEnabled, + this.zoomGesturesEnabled, + }); + + static _AMapOptions fromWidget(AMapWidget map) { + return _AMapOptions( + mapType: map.mapType, + buildingsEnabled: map.buildingsEnabled, + compassEnabled: map.compassEnabled, + labelsEnabled: map.labelsEnabled, + limitBounds: map.limitBounds, + minMaxZoomPreference: map.minMaxZoomPreference, + scaleEnabled: map.scaleEnabled, + touchPoiEnabled: map.touchPoiEnabled, + trafficEnabled: map.trafficEnabled, + rotateGesturesEnabled: map.rotateGesturesEnabled, + scrollGesturesEnabled: map.scrollGesturesEnabled, + tiltGesturesEnabled: map.tiltGesturesEnabled, + zoomGesturesEnabled: map.zoomGesturesEnabled, + customStyleOptions: map.customStyleOptions?.clone(), + myLocationStyleOptions: map.myLocationStyleOptions?.clone(), + ); + } + + Map toMap() { + final Map optionsMap = {}; + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + optionsMap[fieldName] = value; + } + } + + addIfNonNull('mapType', mapType.index); + addIfNonNull('buildingsEnabled', buildingsEnabled); + addIfNonNull('customStyleOptions', customStyleOptions?.clone().toMap()); + addIfNonNull('compassEnabled', compassEnabled); + addIfNonNull('labelsEnabled', labelsEnabled); + addIfNonNull('limitBounds', limitBounds?.toJson()); + addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?.toJson()); + addIfNonNull('scaleEnabled', scaleEnabled); + addIfNonNull('touchPoiEnabled', touchPoiEnabled); + addIfNonNull('trafficEnabled', trafficEnabled); + addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); + addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); + addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled); + addIfNonNull('zoomGesturesEnabled', zoomGesturesEnabled); + addIfNonNull('myLocationStyle', myLocationStyleOptions?.clone().toMap()); + return optionsMap; + } + + Map _updatesMap(_AMapOptions newOptions) { + final Map prevOptionsMap = toMap(); + + return newOptions.toMap()..removeWhere( + (String key, dynamic value) => + (_checkChange(key, prevOptionsMap[key], value)), + ); + } + + bool _checkChange(String key, dynamic preValue, dynamic newValue) { + if (key == 'myLocationStyle' || key == 'customStyleOptions') { + return preValue?.toString() == newValue?.toString(); + } else { + return preValue == newValue; + } + } +} diff --git a/local_plugins/amap_service/lib/core/amap_flutter_platform.dart b/local_plugins/amap_service/lib/core/amap_flutter_platform.dart new file mode 100644 index 000000000..eaef547f3 --- /dev/null +++ b/local_plugins/amap_service/lib/core/amap_flutter_platform.dart @@ -0,0 +1,41 @@ +import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// “amap_flutter_map”平台特定实现必须扩展的接口 +abstract class AMapFlutterPlatform extends PlatformInterface { + static final Object _token = Object(); + AMapFlutterPlatform() : super(token: _token); + static AMapFlutterPlatform _instance = MethodChannelAMapFlutterMap() as AMapFlutterPlatform; + + /// The default instance of [AMapFlutterPlatform] to use. + /// + /// Defaults to [MethodChannelAMapFlutterMap]. + static AMapFlutterPlatform get instance => _instance; + + static set instance(AMapFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// /// Initializes the platform interface with [id]. + /// + /// This method is called when the plugin is first initialized. + Future init(int id) { + throw UnimplementedError('init() has not been implemented.'); + } + + void dispose({required int id}) { + throw UnimplementedError('dispose() has not been implemented.'); + } + + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + PlatformViewCreatedCallback onPlatformViewCreated) { + throw UnimplementedError('buildView() has not been implemented.'); + } +} diff --git a/local_plugins/amap_service/lib/core/map_event.dart b/local_plugins/amap_service/lib/core/map_event.dart new file mode 100644 index 000000000..8e217b07c --- /dev/null +++ b/local_plugins/amap_service/lib/core/map_event.dart @@ -0,0 +1,77 @@ +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/amap_flutter_map.dart'; + +///地图事件处理 +class MapEvent { + /// 地图id + final int mapId; + + ///返回的内容,对应的[MethodCall]中的[[arguments]] + final T value; + + /// 构造一个event + /// + /// `mapId` 当前地图的id + /// `value` 需要传输的值,可以为`null`. + MapEvent(this.mapId, this.value); +} + +///定位回调接口 +class LocationChangedEvent extends MapEvent { + LocationChangedEvent(int mapId, AMapLocation value) : super(mapId, value); +} + +///地图移动回调 +class CameraPositionMoveEvent extends MapEvent { + CameraPositionMoveEvent(int mapId, CameraPosition value) + : super(mapId, value); +} + +///地图移动结束回调 +class CameraPositionMoveEndEvent extends MapEvent { + CameraPositionMoveEndEvent(int mapId, CameraPosition value) + : super(mapId, value); +} + +///点击地图回调 +class MapTapEvent extends MapEvent { + MapTapEvent(int mapId, LatLng value) : super(mapId, value); +} + +///长按地图回调 +class MapLongPressEvent extends MapEvent { + MapLongPressEvent(int mapId, LatLng value) : super(mapId, value); +} + +/// 带位置回调的地图事件 +class _PositionedMapEvent extends MapEvent { + /// 事件中带的位置信息 + final LatLng position; + + /// 构造一个带位置的地图事件, + /// + /// `mapId` 当前地图的id + /// `value` 需要传输的值,可以为`null`. + _PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); +} + +/// [Marker] 的点击事件 +class MarkerTapEvent extends MapEvent { + MarkerTapEvent(int mapId, String markerId) : super(mapId, markerId); +} + +/// [Marker] 的拖拽结束事件,附带拖拽结束时的位置信息[LatLng]. +class MarkerDragEndEvent extends _PositionedMapEvent { + MarkerDragEndEvent(int mapId, LatLng position, String markerId) + : super(mapId, position, markerId); +} + +/// [Polyline] 的点击事件 +class PolylineTapEvent extends MapEvent { + PolylineTapEvent(int mapId, String polylineId) : super(mapId, polylineId); +} + +/// Poi点击事件 +class MapPoiTouchEvent extends MapEvent { + MapPoiTouchEvent(int mapId, AMapPoi poi) : super(mapId, poi); +} diff --git a/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart b/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart new file mode 100644 index 000000000..59c561c5f --- /dev/null +++ b/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart @@ -0,0 +1,274 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; +import 'package:amap_flutter_map/src/types/types.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:stream_transform/stream_transform.dart'; + +import 'map_event.dart'; + +const VIEW_TYPE = 'com.amap.flutter.map'; + +/// 使用[MethodChannel]与Native代码通信的[AMapFlutterPlatform]的实现。 +class MethodChannelAMapFlutterMap implements AMapFlutterPlatform { + final Map _channels = {}; + + MethodChannel channel(int mapId) { + return _channels[mapId]!; + } + + @override + Future init(int mapId) { + MethodChannel? channel = _channels[mapId]; + if (channel == null) { + channel = MethodChannel('amap_flutter_map_$mapId'); + channel.setMethodCallHandler((call) => _handleMethodCall(call, mapId)); + _channels[mapId] = channel; + } + return channel.invokeMethod('map#waitForMap'); + } + + ///更新地图参数 + Future updateMapOptions( + Map newOptions, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'map#update', + { + 'options': newOptions, + }, + ); + } + + /// 更新Marker的数据 + Future updateMarkers( + MarkerUpdates markerUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'markers#update', + markerUpdates.toMap(), + ); + } + + /// 更新polyline的数据 + Future updatePolylines( + PolylineUpdates polylineUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'polylines#update', + polylineUpdates.toMap(), + ); + } + + /// 更新polygon的数据 + Future updatePolygons( + PolygonUpdates polygonUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'polygons#update', + polygonUpdates.toMap(), + ); + } + + @override + void dispose({required int id}) { + if (_channels.containsKey(id)) { + _channels.remove(id); + } + } + + @override + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + void Function(int id) onPlatformViewCreated) { + if (defaultTargetPlatform == TargetPlatform.android) { + creationParams['debugMode'] = kDebugMode; + return AndroidView( + viewType: VIEW_TYPE, + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: VIEW_TYPE, + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return Text('当前平台:$defaultTargetPlatform, 不支持使用高德地图插件'); + } + + // handleMethodCall的`broadcast` + final StreamController _mapEventStreamController = + StreamController.broadcast(); + + // 根据mapid返回相应的event. + Stream _events(int mapId) => + _mapEventStreamController.stream.where((event) => event.mapId == mapId); + + //定位回调 + Stream onLocationChanged({required int mapId}) { + return _events(mapId).whereType(); + } + + //Camera 移动回调 + Stream onCameraMove({required int mapId}) { + return _events(mapId).whereType(); + } + + ///Camera 移动结束回调 + Stream onCameraMoveEnd({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMapTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMapLongPress({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onPoiTouched({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMarkerTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMarkerDragEnd({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onPolylineTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Future _handleMethodCall(MethodCall call, int mapId) async { + switch (call.method) { + case 'location#changed': + try { + _mapEventStreamController.add(LocationChangedEvent( + mapId, AMapLocation.fromMap(call.arguments['location'])!)); + } catch (e) { + print("location#changed error=======>" + e.toString()); + } + break; + + case 'camera#onMove': + try { + _mapEventStreamController.add(CameraPositionMoveEvent( + mapId, CameraPosition.fromMap(call.arguments['position'])!)); + } catch (e) { + print("camera#onMove error===>" + e.toString()); + } + break; + case 'camera#onMoveEnd': + try { + _mapEventStreamController.add(CameraPositionMoveEndEvent( + mapId, CameraPosition.fromMap(call.arguments['position'])!)); + } catch (e) { + print("camera#onMoveEnd error===>" + e.toString()); + } + break; + case 'map#onTap': + _mapEventStreamController + .add(MapTapEvent(mapId, LatLng.fromJson(call.arguments['latLng'])!)); + break; + case 'map#onLongPress': + _mapEventStreamController.add(MapLongPressEvent( + mapId, LatLng.fromJson(call.arguments['latLng'])!)); + break; + + case 'marker#onTap': + _mapEventStreamController.add(MarkerTapEvent( + mapId, + call.arguments['markerId'], + )); + break; + case 'marker#onDragEnd': + _mapEventStreamController.add(MarkerDragEndEvent( + mapId, + LatLng.fromJson(call.arguments['position'])!, + call.arguments['markerId'])); + break; + case 'polyline#onTap': + _mapEventStreamController + .add(PolylineTapEvent(mapId, call.arguments['polylineId'])); + break; + case 'map#onPoiTouched': + try { + _mapEventStreamController.add( + MapPoiTouchEvent(mapId, AMapPoi.fromJson(call.arguments['poi'])!)); + } catch (e) { + print('map#onPoiTouched error===>' + e.toString()); + } + break; + } + } + + ///移动镜头到一个新的位置 + Future moveCamera( + CameraUpdate cameraUpdate, { + required int mapId, + bool animated = true, + int duration = 0, + }) { + return channel(mapId).invokeMethod('camera#move', { + 'cameraUpdate': cameraUpdate.toJson(), + 'animated': animated, + 'duration': duration + }); + } + + ///设置地图每秒渲染的帧数 + Future setRenderFps(int fps, {required int mapId}) { + return channel(mapId) + .invokeMethod('map#setRenderFps', { + 'fps': fps, + }); + } + + ///截屏 + Future takeSnapshot({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#takeSnapshot'); + } + + //获取地图审图号(普通地图) + Future getMapContentApprovalNumber({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#contentApprovalNumber'); + } + + //获取地图审图号(卫星地图) + Future getSatelliteImageApprovalNumber({ + required int mapId, + }) { + return channel(mapId) + .invokeMethod('map#satelliteImageApprovalNumber'); + } + + Future clearDisk({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#clearDisk'); + } +} diff --git a/local_plugins/amap_service/lib/types/base_overlay.dart b/local_plugins/amap_service/lib/types/base_overlay.dart new file mode 100644 index 000000000..f8dbf47f2 --- /dev/null +++ b/local_plugins/amap_service/lib/types/base_overlay.dart @@ -0,0 +1,29 @@ +/// 地图覆盖物基类 +class BaseOverlay { + /// overlay id + late String _id; + + String get id => _id; + + BaseOverlay() { + this._id = this.hashCode.toString(); + } + + void setIdForCopy(String copyId) => _id = copyId; + + BaseOverlay clone() { + throw UnimplementedError( + 'BaseOverlay subClass should implement this methed.'); + } + + Map toMap() { + throw UnimplementedError( + 'BaseOverlay subClass should implement this methed.'); + } +} + +List>? serializeOverlaySet(Set overlays) { + return overlays + .map>((BaseOverlay overlay) => overlay.toMap()) + .toList(); +} diff --git a/local_plugins/amap_service/lib/types/bitmap.dart b/local_plugins/amap_service/lib/types/bitmap.dart new file mode 100644 index 000000000..47e2579c4 --- /dev/null +++ b/local_plugins/amap_service/lib/types/bitmap.dart @@ -0,0 +1,140 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async' show Future; +import 'dart:typed_data' show Uint8List; +import 'dart:ui'; + +import 'package:amap_flutter_base/amap_flutter_base.dart' show AMapUtil; +import 'package:flutter/material.dart' + show ImageConfiguration, AssetImage, AssetBundleImageKey; +import 'package:flutter/services.dart' show AssetBundle; + +import 'package:flutter/foundation.dart' show kIsWeb; + +/// Bitmap工具类 +class BitmapDescriptor { + const BitmapDescriptor._(this._json); + + /// 红色. + static const double hueRed = 0.0; + + /// 橙色. + static const double hueOrange = 30.0; + + /// 黄色. + static const double hueYellow = 60.0; + + /// 绿色. + static const double hueGreen = 120.0; + + /// 青色. + static const double hueCyan = 180.0; + + /// 天蓝色. + static const double hueAzure = 210.0; + + /// 蓝色. + static const double hueBlue = 240.0; + + /// 紫色. + static const double hueViolet = 270.0; + + /// 酒红色. + static const double hueMagenta = 300.0; + + /// 玫瑰红. + static const double hueRose = 330.0; + + /// 创建默认的marker 图标的 bitmap 描述信息对象. + static const BitmapDescriptor defaultMarker = + BitmapDescriptor._(['defaultMarker']); + + /// 创建引用默认着色的BitmapDescriptor + static BitmapDescriptor defaultMarkerWithHue(double hue) { + assert(0.0 <= hue && hue < 360.0); + String filename = "BLUE.png"; + if (hue == hueRed) { + filename = "RED.png"; + } else if (hue == hueOrange) { + filename = "ORANGE.png"; + } else if (hue == hueYellow) { + filename = "YELLOW.png"; + } else if (hue == hueGreen) { + filename = "GREEN.png"; + } else if (hue == hueCyan) { + filename = "CYAN.png"; + } else if (hue == hueAzure) { + filename = "AZURE.png"; + } else if (hue == hueBlue) { + filename = "BLUE.png"; + } else if (hue == hueViolet) { + filename = "VIOLET.png"; + } else if (hue == hueMagenta) { + filename = "MAGENTA.png"; + } else if (hue == hueRose) { + filename = "ROSE.png"; + } + return BitmapDescriptor._([ + 'fromAssetImage', + "packages/amap_flutter_map/res/$filename", + AMapUtil.devicePixelRatio + ]); + } + + ///根据输入的icon路径[iconPath]创建[BitmapDescriptor] + static BitmapDescriptor fromIconPath(String iconPath) { + return BitmapDescriptor._([ + 'fromAsset', + iconPath, + ]); + } + + ///从资源图像创建[BitmapDescriptor]。 + /// + ///Flutter中的assert的资产图像按以下方式存储: + /// https://flutter.dev/docs/development/ui/assets and images声明-分辨率感知图像资源 + ///该方法考虑了各种资产解决方案 + ///并根据dpi将图像缩放到正确的分辨率。 + ///将`mipmaps1设置为false可加载图像的精确dpi版本,默认情况下,`mipmap`为true。 + static Future fromAssetImage( + ImageConfiguration configuration, + String assetName, { + AssetBundle? bundle, + String? package, + bool mipmaps = true, + }) async { + if (!mipmaps && configuration.devicePixelRatio != null) { + return BitmapDescriptor._([ + 'fromAssetImage', + assetName, + configuration.devicePixelRatio, + ]); + } + final AssetImage assetImage = + AssetImage(assetName, package: package, bundle: bundle); + final AssetBundleImageKey assetBundleImageKey = + await assetImage.obtainKey(configuration); + final Size? size = configuration.size; + return BitmapDescriptor._([ + 'fromAssetImage', + assetBundleImageKey.name, + assetBundleImageKey.scale, + if (kIsWeb && size != null) + [ + size.width, + size.height, + ], + ]); + } + + /// 根据将PNG图片转换后的二进制数据[byteData]创建BitmapDescriptor + static BitmapDescriptor fromBytes(Uint8List byteData) { + return BitmapDescriptor._(['fromBytes', byteData]); + } + + final dynamic _json; + + dynamic toMap() => _json; +} diff --git a/local_plugins/amap_service/lib/types/camera.dart b/local_plugins/amap_service/lib/types/camera.dart new file mode 100644 index 000000000..2a4b5bc3c --- /dev/null +++ b/local_plugins/amap_service/lib/types/camera.dart @@ -0,0 +1,150 @@ +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:flutter/cupertino.dart'; + +/// 相机位置,包含可视区域的位置参数。 +class CameraPosition { + /// 构造一个CameraPosition 对象 + /// + /// 如果[bearing], [target], [tilt], 或者 [zoom] 为null时会返回[AssertionError] + const CameraPosition({ + this.bearing = 0.0, + required this.target, + this.tilt = 0.0, + this.zoom = 10, + }); + + /// 可视区域指向的方向,以角度为单位,从正北向逆时针方向计算,从0 度到360 度。 + final double bearing; + + /// 目标位置的屏幕中心点经纬度坐标。 + final LatLng target; + + /// 目标可视区域的倾斜度,以角度为单位。范围从0到360度 + final double tilt; + + /// 目标可视区域的缩放级别 + final double zoom; + + /// 将[CameraPosition]装换成Map + /// + /// 主要在插件内部使用 + dynamic toMap() => { + 'bearing': bearing, + 'target': target.toJson(), + 'tilt': tilt, + 'zoom': zoom, + }; + + /// 从Map转换成[CameraPosition] + /// + /// 主要在插件内部使用 + static CameraPosition? fromMap(dynamic json) { + if (json == null || !(json is Map)) { + return null; + } + final LatLng? target = LatLng.fromJson(json['target']); + if (target == null) { + return null; + } + return CameraPosition( + bearing: json['bearing'], + target: target, + tilt: json['tilt'], + zoom: json['zoom'], + ); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraPosition typedOther = other; + return bearing == typedOther.bearing && + target == typedOther.target && + tilt == typedOther.tilt && + zoom == typedOther.zoom; + } + + @override + int get hashCode => Object.hash(bearing, target, tilt, zoom); + + @override + String toString() => + 'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; +} + +/// 描述地图状态将要发生的变化 +class CameraUpdate { + CameraUpdate._(this._json); + + ///返回根据新的[CameraPosition]移动后的[CameraUpdate]. + /// + ///主要用于改变地图的中心点、缩放级别、倾斜角、角度等信息 + static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { + return CameraUpdate._([ + 'newCameraPosition', + cameraPosition.toMap(), + ]); + } + + ///移动到一个新的位置点[latLng] + /// + ///主要用于改变地图的中心点 + static CameraUpdate newLatLng(LatLng latLng) { + return CameraUpdate._(['newLatLng', latLng.toJson()]); + } + + ///根据指定到摄像头显示范围[bounds]和边界值[padding]创建一个CameraUpdate对象 + /// + ///主要用于根据指定的显示范围[bounds]以最佳的视野显示地图 + static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { + return CameraUpdate._([ + 'newLatLngBounds', + bounds.toJson(), + padding, + ]); + } + + /// 根据指定的新的位置[latLng]和缩放级别[zoom]创建一个CameraUpdate对象 + /// + /// 主要用于同时改变中心点和缩放级别 + static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { + return CameraUpdate._(['newLatLngZoom', latLng.toJson(), zoom]); + } + + /// 按照指定到像素点[dx]和[dy]移动地图中心点 + /// + /// [dx]是水平移动的像素数。正值代表可视区域向右移动,负值代表可视区域向左移动 + /// + /// [dy]是垂直移动的像素数。正值代表可视区域向下移动,负值代表可视区域向上移动 + /// + /// 返回包含x,y方向上移动像素数的cameraUpdate对象。 + static CameraUpdate scrollBy(double dx, double dy) { + return CameraUpdate._(['scrollBy', dx, dy]); + } + + /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 + /// + ///主要用于放大地图缩放级别,在当前地图显示的级别基础上加1 + static CameraUpdate zoomIn() { + return CameraUpdate._(['zoomIn']); + } + + /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 + /// + /// 主要用于减少地图缩放级别,在当前地图显示的级别基础上减1 + static CameraUpdate zoomOut() { + return CameraUpdate._(['zoomOut']); + } + + /// 创建一个指定缩放级别[zoom]的CameraUpdate对象 + /// + /// 主要用于设置地图缩放级别 + static CameraUpdate zoomTo(double zoom) { + return CameraUpdate._(['zoomTo', zoom]); + } + + final dynamic _json; + + dynamic toJson() => _json; +} diff --git a/local_plugins/amap_service/lib/types/marker.dart b/local_plugins/amap_service/lib/types/marker.dart new file mode 100644 index 000000000..2c5190452 --- /dev/null +++ b/local_plugins/amap_service/lib/types/marker.dart @@ -0,0 +1,249 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show Offset; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; +import 'bitmap.dart'; + +/// Marker拖动回调 +typedef void MarkerDragEndCallback(String id, LatLng endPosition); + +///Marker的气泡 +/// +///Android和iOS的实现机制有差异,仅在接口层面拉齐,效果一致 +class InfoWindow { + /// 为 [Marker] 产生一个不可修改的文本气泡. + const InfoWindow({this.title, this.snippet}); + + /// 无文本的气泡 + static const InfoWindow noText = InfoWindow(); + + /// 气泡的title + final String? title; + + /// 气泡的详细信息 + final String? snippet; + + /// 气泡copy方法 + /// + InfoWindow copyWith({String? titleParam, String? snippetParam}) { + return InfoWindow( + title: titleParam ?? title, + snippet: snippetParam ?? snippet, + ); + } + + Map _toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('title', title); + addIfPresent('snippet', snippet); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! InfoWindow) { + return false; + } + final InfoWindow typedOther = other; + return title == typedOther.title && snippet == typedOther.snippet; + } + + @override + int get hashCode => Object.hash(title, snippet); + + @override + String toString() { + return 'InfoWindow{title: $title, snippet: $snippet}'; + } +} + +/// 点覆盖物的类 +class Marker extends BaseOverlay { + Marker({ + required this.position, + double alpha = 1.0, + Offset anchor = const Offset(0.5, 1.0), + this.clickable = true, + this.draggable = false, + this.icon = BitmapDescriptor.defaultMarker, + this.infoWindowEnable = true, + this.infoWindow = InfoWindow.noText, + this.rotation = 0.0, + this.visible = true, + this.zIndex = 0.0, + this.onTap, + this.onDragEnd, + }) : this.alpha = + // ignore: unnecessary_null_comparison + (alpha != null ? (alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)) : alpha), + // ignore: unnecessary_null_comparison + this.anchor = + (anchor == null + ? Offset(0.5, 1.0) + : ((anchor.dx < 0 || + anchor.dx > 1 || + anchor.dy < 0 || + anchor.dy > 1) + ? Offset(0.5, 1.0) + : anchor)), + super(); + + /// 透明度 + final double alpha; + + /// 覆盖物视图相对地图上的经纬度位置的锚点 + final Offset anchor; + + /// 是否可点击,默认为true + final bool clickable; + + /// 是否可拖拽,默认为false + final bool draggable; + + /// 覆盖物的图标 + final BitmapDescriptor icon; + + /// 是否显示气泡,如果为true,则点击[Marker]后,会显示该气泡[InfoWindow] + /// 如果为false,则始终不会显示该气泡 + final bool infoWindowEnable; + + /// 覆盖物上的气泡,当被点击时,如果infoWindowEnable为true,则会显示出来 + final InfoWindow infoWindow; + + /// 位置,不能为空 + final LatLng position; + + /// 旋转角度,以锚点为中心,顺时针旋转(单位:度数) + /// + /// 注意:iOS端目前仅支持绕marker中心点旋转 + final double rotation; + + /// 是否可见 + final bool visible; + + /// z轴的值,用于调整该覆盖物的相对绘制层级关系 + /// 值越小,图层越靠下,iOS该值不支持动态修改,仅能在初始化时指定 + final double zIndex; + + /// 回调的参数是对应的id + final ArgumentCallback? onTap; + + /// Marker被拖拽结束的回调 + final MarkerDragEndCallback? onDragEnd; + + /// copy的真正复制的参数,主要用于需要修改某个属性参数时使用 + Marker copyWith({ + double? alphaParam, + Offset? anchorParam, + bool? clickableParam, + bool? draggableParam, + BitmapDescriptor? iconParam, + bool? infoWindowEnableParam, + InfoWindow? infoWindowParam, + LatLng? positionParam, + double? rotationParam, + bool? visibleParam, + ArgumentCallback? onTapParam, + MarkerDragEndCallback? onDragEndParam, + }) { + Marker copyMark = Marker( + alpha: alphaParam ?? alpha, + anchor: anchorParam ?? anchor, + clickable: clickableParam ?? clickable, + draggable: draggableParam ?? draggable, + icon: iconParam ?? icon, + infoWindowEnable: infoWindowEnableParam ?? infoWindowEnable, + infoWindow: infoWindowParam ?? infoWindow, + position: positionParam ?? position, + rotation: rotationParam ?? rotation, + visible: visibleParam ?? visible, + zIndex: zIndex, + onTap: onTapParam ?? onTap, + onDragEnd: onDragEndParam ?? onDragEnd, + ); + copyMark.setIdForCopy(id); + return copyMark; + } + + Marker clone() => copyWith(); + + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + addIfPresent('alpha', alpha); + addIfPresent('anchor', _offsetToJson(anchor)); + addIfPresent('clickable', clickable); + addIfPresent('draggable', draggable); + addIfPresent('icon', icon.toMap()); + addIfPresent('infoWindowEnable', infoWindowEnable); + addIfPresent('infoWindow', infoWindow._toMap()); + addIfPresent('position', position.toJson()); + addIfPresent('rotation', rotation); + addIfPresent('visible', visible); + addIfPresent('zIndex', zIndex); + return json; + } + + dynamic _offsetToJson(Offset offset) { + return [offset.dx, offset.dy]; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! Marker) return false; + final Marker typedOther = other; + return id == typedOther.id && + alpha == typedOther.alpha && + anchor == typedOther.anchor && + clickable == typedOther.clickable && + draggable == typedOther.draggable && + icon == typedOther.icon && + infoWindowEnable == typedOther.infoWindowEnable && + infoWindow == typedOther.infoWindow && + position == typedOther.position && + rotation == typedOther.rotation && + visible == typedOther.visible && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => super.hashCode; + + @override + String toString() { + return 'Marker{id: $id, alpha: $alpha, anchor: $anchor, ' + 'clickable: $clickable, draggable: $draggable,' + 'icon: $icon, infoWindowEnable: $infoWindowEnable, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' + 'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; + } +} + +Map keyByMarkerId(Iterable markers) { + return Map.fromEntries( + markers.map( + (Marker marker) => MapEntry(marker.id, marker.clone()), + ), + ); +} diff --git a/local_plugins/amap_service/lib/types/marker_updates.dart b/local_plugins/amap_service/lib/types/marker_updates.dart new file mode 100644 index 000000000..e69ad4b1c --- /dev/null +++ b/local_plugins/amap_service/lib/types/marker_updates.dart @@ -0,0 +1,105 @@ +import 'dart:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'types.dart'; +import 'marker.dart'; + +/// 用以描述Marker的更新项 +class MarkerUpdates { + /// 根据之前的marker列表[previous]和当前的marker列表[current]创建[MakerUpdates]. + MarkerUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousMarkers = keyByMarkerId(previous); + final Map currentMarkers = keyByMarkerId(current); + + final Set prevMarkerIds = previousMarkers.keys.toSet(); + final Set currentMarkerIds = currentMarkers.keys.toSet(); + + Marker idToCurrentMarker(String id) { + return currentMarkers[id]!; + } + + final Set _markerIdsToRemove = prevMarkerIds.difference( + currentMarkerIds, + ); + + final Set _markersToAdd = + currentMarkerIds + .difference(prevMarkerIds) + .map(idToCurrentMarker) + .toSet(); + + bool hasChanged(Marker current) { + final Marker? previous = previousMarkers[current.id]; + return current != previous; + } + + final Set _markersToChange = + currentMarkerIds + .intersection(prevMarkerIds) + .map(idToCurrentMarker) + .where(hasChanged) + .toSet(); + + markersToAdd = _markersToAdd; + markerIdsToRemove = _markerIdsToRemove; + markersToChange = _markersToChange; + } + + /// 想要添加的marker集合. + Set? markersToAdd; + + /// 想要删除的marker的id集合 + Set? markerIdsToRemove; + + /// 想要更新的marker集合. + Set? markersToChange; + + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('markersToAdd', serializeOverlaySet(markersToAdd!)); + addIfNonNull('markersToChange', serializeOverlaySet(markersToChange!)); + addIfNonNull('markerIdsToRemove', markerIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! MarkerUpdates) return false; + final MarkerUpdates typedOther = other; + return setEquals(markersToAdd, typedOther.markersToAdd) && + setEquals(markerIdsToRemove, typedOther.markerIdsToRemove) && + setEquals(markersToChange, typedOther.markersToChange); + } + + @override + int get hashCode => + Object.hash(markersToAdd, markerIdsToRemove, markersToChange); + + @override + String toString() { + return '_MarkerUpdates{markersToAdd: $markersToAdd, ' + 'markerIdsToRemove: $markerIdsToRemove, ' + 'markersToChange: $markersToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/polygon.dart b/local_plugins/amap_service/lib/types/polygon.dart new file mode 100644 index 000000000..a780cfe98 --- /dev/null +++ b/local_plugins/amap_service/lib/types/polygon.dart @@ -0,0 +1,120 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show listEquals; +import 'package:flutter/material.dart' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; +import 'polyline.dart'; + +/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 +class Polygon extends BaseOverlay { + /// 默认构造函数 + Polygon( + {required this.points, + double strokeWidth = 10, + this.strokeColor = const Color(0xCC00BFFF), + this.fillColor = const Color(0xC487CEFA), + this.visible = true, + this.joinType = JoinType.bevel}) + : assert(points.length > 0), + this.strokeWidth = (strokeWidth <= 0 ? 10 : strokeWidth), + super(); + + /// 覆盖物的坐标点数组,不能为空 + final List points; + + /// 边框宽度,单位为逻辑像素,同Android中的dp,iOS中的point + final double strokeWidth; + + /// 边框颜色,默认值为(0xCCC4E0F0) + final Color strokeColor; + + /// 填充颜色,默认值为(0xC4E0F0CC) + final Color fillColor; + + /// 是否可见 + final bool visible; + + /// 连接点类型,该参数不支持copy时修改,仅能在初始化时设置一次 + final JoinType joinType; + + /// 实际copy函数 + Polygon copyWith({ + List? pointsParam, + double? strokeWidthParam, + Color? strokeColorParam, + Color? fillColorParam, + bool? visibleParam, + }) { + Polygon copyPolyline = Polygon( + points: pointsParam ?? points, + strokeWidth: strokeWidthParam ?? strokeWidth, + strokeColor: strokeColorParam ?? strokeColor, + fillColor: fillColorParam ?? fillColor, + visible: visibleParam ?? visible, + joinType: joinType, + ); + copyPolyline.setIdForCopy(id); + return copyPolyline; + } + + Polygon clone() => copyWith(); + + /// 转换成可以序列化的map + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + json['points'] = _pointsToJson(); + addIfPresent('strokeWidth', strokeWidth); + addIfPresent('strokeColor', strokeColor.value); + addIfPresent('fillColor', fillColor.value); + addIfPresent('visible', visible); + addIfPresent('joinType', joinType.index); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is !Polygon) return false; + final Polygon typedOther = other; + return id == typedOther.id && + listEquals(points, typedOther.points) && + strokeWidth == typedOther.strokeWidth && + strokeColor == typedOther.strokeColor && + fillColor == typedOther.fillColor && + visible == typedOther.visible && + joinType == typedOther.joinType; + } + + @override + int get hashCode => super.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } +} + +Map keyByPolygonId(Iterable polylines) { + // ignore: unnecessary_null_comparison + if (polylines == null) { + return {}; + } + return Map.fromEntries(polylines.map((Polygon polyline) => + MapEntry(polyline.id, polyline.clone()))); +} diff --git a/local_plugins/amap_service/lib/types/polygon_updates.dart b/local_plugins/amap_service/lib/types/polygon_updates.dart new file mode 100644 index 000000000..0584b0d9a --- /dev/null +++ b/local_plugins/amap_service/lib/types/polygon_updates.dart @@ -0,0 +1,108 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; +import 'types.dart'; + +/// 该类主要用以描述[Polygon]的增删改等更新操作 +class PolygonUpdates { + /// 通过Polygon的前后更新集合构造一个PolygonUpdates + PolygonUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousPolygons = keyByPolygonId(previous); + final Map currentPolygons = keyByPolygonId(current); + + final Set prevPolygonIds = previousPolygons.keys.toSet(); + final Set currentPolygonIds = currentPolygons.keys.toSet(); + + Polygon idToCurrentPolygon(String id) { + return currentPolygons[id]!; + } + + final Set _polygonIdsToRemove = prevPolygonIds.difference( + currentPolygonIds, + ); + + final Set _polygonsToAdd = + currentPolygonIds + .difference(prevPolygonIds) + .map(idToCurrentPolygon) + .toSet(); + + bool hasChanged(Polygon current) { + final Polygon previous = previousPolygons[current.id]!; + return current != previous; + } + + final Set _polygonsToChange = + currentPolygonIds + .intersection(prevPolygonIds) + .map(idToCurrentPolygon) + .where(hasChanged) + .toSet(); + + polygonsToAdd = _polygonsToAdd; + polygonIdsToRemove = _polygonIdsToRemove; + polygonsToChange = _polygonsToChange; + } + + /// 想要添加的polygon对象集合. + Set? polygonsToAdd; + + /// 想要删除的polygon的id集合 + Set? polygonIdsToRemove; + + /// 想要更新的polygon对象集合 + Set? polygonsToChange; + + /// 转换成可以序列化的map + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polygonsToAdd', serializeOverlaySet(polygonsToAdd!)); + addIfNonNull('polygonsToChange', serializeOverlaySet(polygonsToChange!)); + addIfNonNull('polygonIdsToRemove', polygonIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! PolygonUpdates) return false; + final PolygonUpdates typedOther = other; + return setEquals(polygonsToAdd, typedOther.polygonsToAdd) && + setEquals(polygonIdsToRemove, typedOther.polygonIdsToRemove) && + setEquals(polygonsToChange, typedOther.polygonsToChange); + } + + @override + int get hashCode => + Object.hash(polygonsToAdd, polygonIdsToRemove, polygonsToChange); + + @override + String toString() { + return '_PolygonUpdates{polygonsToAdd: $polygonsToAdd, ' + 'polygonIdsToRemove: $polygonIdsToRemove, ' + 'polygonsToChange: $polygonsToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/polyline.dart b/local_plugins/amap_service/lib/types/polyline.dart new file mode 100644 index 000000000..58506ec1a --- /dev/null +++ b/local_plugins/amap_service/lib/types/polyline.dart @@ -0,0 +1,198 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:amap_flutter_map/src/types/bitmap.dart'; +import 'package:flutter/foundation.dart' show listEquals; +import 'package:flutter/material.dart' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; + +/// 虚线类型 +enum DashLineType { + /// 不画虚线 + none, + + /// 方块样式 + square, + + /// 圆点样式 + circle, +} + +/// 线头类型 +enum CapType { + /// 普通头 + butt, + + /// 扩展头 + square, + + /// 箭头 + arrow, + + /// 圆形头 + round, +} + +/// 连接点类型 +enum JoinType { + /// 斜面连接点 + bevel, + + /// 斜接连接点 + miter, + + /// 圆角连接点 + round, +} + +/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 +class Polyline extends BaseOverlay { + /// 默认构造函数 + Polyline({ + required this.points, + double width = 10, + this.visible = true, + this.geodesic = false, + double alpha = 1.0, + this.dashLineType = DashLineType.none, + this.capType = CapType.butt, + this.joinType = JoinType.bevel, + this.customTexture, + this.onTap, + this.color = const Color(0xCCC4E0F0), + }) : assert(points.length > 0), + this.width = (width <= 0 ? 10 : width), + this.alpha =(alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)), + super(); + + /// 覆盖物的坐标点数组,points不能为空 + final List points; + + /// 线宽,单位为逻辑像素,同Android中的dp,iOS中的point + final double width; + + /// 是否可见 + final bool visible; + + /// 透明度 + final double alpha; + + /// 覆盖物颜色,默认值为(0xCCC4E0F0). + final Color color; + + /// 自定义纹理图片,注意: 如果设置了自定义纹理图片,则color的设置将无效; + final BitmapDescriptor? customTexture; + + /// 是否为大地曲线 + final bool geodesic; + + /// 虚线类型 + final DashLineType dashLineType; + + /// 连接点类型 + final JoinType joinType; + + /// 线头类型 + final CapType capType; + + /// 点击回调(回调参数为id) + final ArgumentCallback? onTap; + + /// 实际copy函数 + Polyline copyWith({ + List? pointsParam, + double? widthParam, + int? zIndexParam, + bool? visibleParam, + double? alphaParam, + DashLineType? dashLineTypeParam, + CapType? capTypeParam, + JoinType? joinTypeParam, + BitmapDescriptor? customTextureParam, + ArgumentCallback? onTapParam, + Color? colorParam, + }) { + Polyline copyPolyline = Polyline( + points: pointsParam ?? points, + width: widthParam ?? width, + visible: visibleParam ?? visible, + geodesic: geodesic, + alpha: alphaParam ?? alpha, + dashLineType: dashLineTypeParam ?? dashLineType, + capType: capTypeParam ?? capType, + joinType: joinTypeParam ?? joinType, + customTexture: customTextureParam ?? customTexture, + onTap: onTapParam ?? onTap, + color: colorParam ?? color, + ); + copyPolyline.setIdForCopy(id); + return copyPolyline; + } + + Polyline clone() => copyWith(); + + /// 将对象转换为可序列化的map. + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + json['points'] = _pointsToJson(); + addIfPresent('width', width); + addIfPresent('visible', visible); + addIfPresent('geodesic', geodesic); + addIfPresent('alpha', alpha); + addIfPresent('dashLineType', dashLineType.index); + addIfPresent('capType', capType.index); + addIfPresent('joinType', joinType.index); + addIfPresent('customTexture', customTexture?.toMap()); + addIfPresent('color', color.value); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is !Polyline) return false; + final Polyline typedOther = other; + return id == typedOther.id && + listEquals(points, typedOther.points) && + width == typedOther.width && + visible == typedOther.visible && + geodesic == typedOther.geodesic && + alpha == typedOther.alpha && + dashLineType == typedOther.dashLineType && + capType == typedOther.capType && + joinType == typedOther.joinType && + color == typedOther.color; + } + + @override + int get hashCode => super.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } +} + +Map keyByPolylineId(Iterable polylines) { + // ignore: unnecessary_null_comparison + if (polylines == null) { + return {}; + } + return Map.fromEntries(polylines.map((Polyline polyline) => + MapEntry(polyline.id, polyline.clone()))); +} diff --git a/local_plugins/amap_service/lib/types/polyline_updates.dart b/local_plugins/amap_service/lib/types/polyline_updates.dart new file mode 100644 index 000000000..29d485d15 --- /dev/null +++ b/local_plugins/amap_service/lib/types/polyline_updates.dart @@ -0,0 +1,108 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show setEquals; + +import 'polyline.dart'; +import 'types.dart'; + +/// 该类主要用以描述[Polyline]的增删改等更新操作 +class PolylineUpdates { + /// 通过polyline的前后更新集合构造一个polylineUpdates + PolylineUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousPolylines = keyByPolylineId(previous); + final Map currentPolylines = keyByPolylineId(current); + + final Set prevPolylineIds = previousPolylines.keys.toSet(); + final Set currentPolylineIds = currentPolylines.keys.toSet(); + + Polyline idToCurrentPolyline(String id) { + return currentPolylines[id]!; + } + + final Set _polylineIdsToRemove = prevPolylineIds.difference( + currentPolylineIds, + ); + + final Set _polylinesToAdd = + currentPolylineIds + .difference(prevPolylineIds) + .map(idToCurrentPolyline) + .toSet(); + + bool hasChanged(Polyline current) { + final Polyline previous = previousPolylines[current.id]!; + return current != previous; + } + + final Set _polylinesToChange = + currentPolylineIds + .intersection(prevPolylineIds) + .map(idToCurrentPolyline) + .where(hasChanged) + .toSet(); + + polylinesToAdd = _polylinesToAdd; + polylineIdsToRemove = _polylineIdsToRemove; + polylinesToChange = _polylinesToChange; + } + + /// 用于添加polyline的集合 + Set? polylinesToAdd; + + /// 需要删除的plyline的id集合 + Set? polylineIdsToRemove; + + /// 用于更新polyline的集合 + Set? polylinesToChange; + + /// 将对象装换为可序列化的对象 + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polylinesToAdd', serializeOverlaySet(polylinesToAdd!)); + addIfNonNull('polylinesToChange', serializeOverlaySet(polylinesToChange!)); + addIfNonNull('polylineIdsToRemove', polylineIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! PolylineUpdates) return false; + final PolylineUpdates typedOther = other; + return setEquals(polylinesToAdd, typedOther.polylinesToAdd) && + setEquals(polylineIdsToRemove, typedOther.polylineIdsToRemove) && + setEquals(polylinesToChange, typedOther.polylinesToChange); + } + + @override + int get hashCode => + Object.hash(polylinesToAdd, polylineIdsToRemove, polylinesToChange); + + @override + String toString() { + return '_PolylineUpdates{polylinesToAdd: $polylinesToAdd, ' + 'polylineIdsToRemove: $polylineIdsToRemove, ' + 'polylinesToChange: $polylinesToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/types.dart b/local_plugins/amap_service/lib/types/types.dart new file mode 100644 index 000000000..e62300549 --- /dev/null +++ b/local_plugins/amap_service/lib/types/types.dart @@ -0,0 +1,11 @@ +export 'camera.dart'; +export 'ui.dart'; +export 'base_overlay.dart'; +export 'marker.dart'; +export 'marker_updates.dart'; +export 'polyline.dart'; +export 'polyline_updates.dart'; +export 'polygon.dart'; +export 'polygon_updates.dart'; +export 'camera.dart'; +export 'bitmap.dart'; diff --git a/local_plugins/amap_service/lib/types/ui.dart b/local_plugins/amap_service/lib/types/ui.dart new file mode 100644 index 000000000..e33409197 --- /dev/null +++ b/local_plugins/amap_service/lib/types/ui.dart @@ -0,0 +1,257 @@ +import 'dart:typed_data'; +import 'dart:ui' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/amap_flutter_map.dart'; + +/// 地图类型 +enum MapType { + /// 普通地图 + normal, + + /// 卫星地图 + satellite, + + /// 夜间视图 + night, + + /// 导航视图 + navi, + + /// 公交视图 + bus, +} + +// 设置摄像机的边界. +class CameraTargetBounds { + /// 使用指定的边界框或空值创建摄影机目标边界 + /// + /// 设置为null时代表不指定边界 + const CameraTargetBounds(this.bounds); + + /// 摄像机的边界. + /// + /// null代表不指定边界 + final LatLngBounds? bounds; + + /// 取消指定边界 + static const CameraTargetBounds unbounded = CameraTargetBounds(null); + + /// 转换成json对象 + dynamic toJson() => [bounds?.toJson()]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraTargetBounds typedOther = other; + return bounds == typedOther.bounds; + } + + @override + int get hashCode => bounds.hashCode; + + @override + String toString() { + return 'CameraTargetBounds(bounds: $bounds)'; + } +} + +/// 地图最大最小缩放级别的封装对象 +class MinMaxZoomPreference { + /// 为地图创建一个不可变的最大最小缩放范围 + /// + /// 缩放级别范围为[3, 20],超出范围取边界值 + /// + const MinMaxZoomPreference(double minZoom, double maxZoom) + : this.minZoom = + ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) + ? maxZoom + : minZoom), + this.maxZoom = + ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) + ? minZoom + : maxZoom); + + /// 最小zoomLevel + final double? minZoom; + + /// 最大zoomLevel + final double? maxZoom; + + /// 高德地图默认zoomLevel的范围. + static const MinMaxZoomPreference defaultPreference = MinMaxZoomPreference( + 3, + 20, + ); + + /// JSON序列化. + dynamic toJson() => [minZoom, maxZoom]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final MinMaxZoomPreference typedOther = other; + return minZoom == typedOther.minZoom && maxZoom == typedOther.maxZoom; + } + + @override + int get hashCode => Object.hash(minZoom, maxZoom); + + @override + String toString() { + return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; + } +} + +///定位小蓝点配置项 +class MyLocationStyleOptions { + ///是否显示定位小蓝点 + bool enabled; + + ///精度圈填充色 + Color? circleFillColor; + + ///精度圈边框色 + Color? circleStrokeColor; + + ///精度圈边框宽度 + double? circleStrokeWidth; + + ///小蓝点图标 + BitmapDescriptor? icon; + + MyLocationStyleOptions( + this.enabled, { + this.circleFillColor, + this.circleStrokeColor, + this.circleStrokeWidth, + this.icon, + }); + + MyLocationStyleOptions clone() { + return MyLocationStyleOptions( + enabled, + circleFillColor: circleFillColor, + circleStrokeColor: circleStrokeColor, + circleStrokeWidth: circleStrokeWidth, + icon: icon, + ); + } + + static MyLocationStyleOptions? fromMap(dynamic json) { + if (null == json) { + return null; + } + return MyLocationStyleOptions( + json['enabled'] ?? false, + circleFillColor: json['circleFillColor'] ?? null, + circleStrokeColor: json['circleStrokeColor'] ?? null, + circleStrokeWidth: json['circleStrokeWidth'] ?? null, + icon: json['icon'] ?? null, + ); + } + + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('enabled', enabled); + addIfPresent('circleFillColor', circleFillColor?.value); + addIfPresent('circleStrokeColor', circleStrokeColor?.value); + addIfPresent('circleStrokeWidth', circleStrokeWidth); + addIfPresent('icon', icon?.toMap()); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + if (other is! MyLocationStyleOptions) return false; + final MyLocationStyleOptions typedOther = other; + return enabled == typedOther.enabled && + circleFillColor == typedOther.circleFillColor && + circleStrokeColor == typedOther.circleStrokeColor && + icon == typedOther.icon; + } + + @override + String toString() { + return 'MyLocationOptionsStyle{' + 'enabled: $enabled,' + 'circleFillColor: $circleFillColor,' + 'circleStrokeColor: $circleStrokeColor,' + 'icon: $icon, }'; + } + + @override + int get hashCode => + Object.hash(enabled, circleFillColor, circleStrokeColor, icon); +} + +///地图自定义样式 +class CustomStyleOptions { + ///开关项,是否开启自定义地图 + bool enabled; + + ///自定义样式的二进制数据,对应下载的自定义地图文件中的style.data中的二进制数据 + Uint8List? styleData; + + ///自定义扩展样式的二进制数据,对应下载的自定义地图文件中的style_extra.data中的二进制数据 + Uint8List? styleExtraData; + + CustomStyleOptions(this.enabled, {this.styleData, this.styleExtraData}); + + static CustomStyleOptions? fromMap(dynamic json) { + if (json == null) { + return null; + } + return CustomStyleOptions( + json['enabled'] ?? false, + styleData: json['styleData'] ?? null, + styleExtraData: json['styleExtraData'] ?? null, + ); + } + + dynamic toMap() { + final Map json = {}; + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('enabled', enabled); + addIfPresent('styleData', styleData); + addIfPresent('styleExtraData', styleExtraData); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + if (other is! CustomStyleOptions) return false; + final CustomStyleOptions typedOther = other; + return enabled == typedOther.enabled && + styleData == typedOther.styleData && + styleExtraData == typedOther.styleExtraData; + } + + @override + int get hashCode => Object.hash(enabled, styleData, styleExtraData); + + CustomStyleOptions clone() { + return CustomStyleOptions( + enabled, + styleData: styleData, + styleExtraData: styleExtraData, + ); + } +} diff --git a/local_plugins/amap_service/pubspec.yaml b/local_plugins/amap_service/pubspec.yaml index 5cb50340b..f7999f100 100644 --- a/local_plugins/amap_service/pubspec.yaml +++ b/local_plugins/amap_service/pubspec.yaml @@ -16,6 +16,10 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + amap_flutter_base: ^3.0.0 + amap_flutter_map: ^3.0.0 + deepvoice_utils: + path: ../deepvoice_utils # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 48bc9bac10dbdc56e568879768292aa37ce50fe6..e43ebb023b9841b545f849e66b8205d102fc46e3 100644 GIT binary patch literal 17 TcmZQ}Dp$CADo{Lz0Rr>^DKZ0Z literal 17 TcmZQ}Dp$CADo{Lz0Rr>@DKP_X diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties index d3c4e6543..8aeb8c861 100644 --- a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties +++ b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties @@ -1,2 +1,2 @@ -#Sun May 11 13:39:53 IST 2025 -gradle.version=8.10 +#Tue May 20 09:53:38 CST 2025 +gradle.version=8.9 From 11d43636ffb0283334621b0f0a8b32a0a4403036 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 20 May 2025 10:44:22 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- local_plugins/agent_service/android/build.gradle.kts | 2 +- .../kotlin/com/yunqiinnovation/agent_service/AgentService.kt | 3 ++- local_plugins/amap_service/android/build.gradle | 3 --- .../com/yunqiinnovation/amap_service/NavigationService.kt | 4 ++-- local_plugins/amap_service/pubspec.yaml | 2 -- pubspec.yaml | 3 ++- 6 files changed, 7 insertions(+), 10 deletions(-) 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 3f104f02a..38f3ab2b3 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,6 +7,7 @@ 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.NavigationServiceStarter import android.util.Log import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* @@ -1189,7 +1190,7 @@ object AgentService : CoroutineScope { // 如果 playlist 中有有效的歌曲,开始播放 if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { Log.i(TAG, "启动导航服务 ${start} ${end}") - // NavigationServiceStarter.startServiceWithCommand(context, command = "start", startpos = start, endpos = end) + NavigationServiceStarter.startServiceWithCommand(context, command = "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 d9568dd2d..4bd0a8a1d 100644 --- a/local_plugins/amap_service/android/build.gradle +++ b/local_plugins/amap_service/android/build.gradle @@ -65,6 +65,3 @@ android { } } } -dependencies { - implementation(project(":deepvoice_utils")) -} 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 093e14137..2fd8947f9 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 @@ -262,8 +262,8 @@ class NavigationService: Service() { override fun onCalculateRouteSuccess(ids: IntArray?) { Log.e(TAG, "onCalculateRouteSuccess! $ids") - AMapNavi.getInstance(this).startNavi(NaviType.GPS); - aMapNavi.startNavi(NaviType.EMULATOR) + // AMapNavi.getInstance(this).startNavi(1); + aMapNavi.startNavi(1) } override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { diff --git a/local_plugins/amap_service/pubspec.yaml b/local_plugins/amap_service/pubspec.yaml index f7999f100..66d9bcad4 100644 --- a/local_plugins/amap_service/pubspec.yaml +++ b/local_plugins/amap_service/pubspec.yaml @@ -18,8 +18,6 @@ dev_dependencies: flutter_lints: ^5.0.0 amap_flutter_base: ^3.0.0 amap_flutter_map: ^3.0.0 - deepvoice_utils: - path: ../deepvoice_utils # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/pubspec.yaml b/pubspec.yaml index 7a8bbb5c2..875844f39 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,7 +85,8 @@ dependencies: path: local_plugins/agent_service music_service: path: local_plugins/music_service - + amap_service: + path: local_plugins/amap_service dev_dependencies: flutter_test: From 36eacf3a0b95731866b16ff5cfad962b9f2c19b9 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 20 May 2025 21:37:41 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/bindings/initial_binding.dart | 3 + lib/data/services/navigation_manager.dart | 8 +- .../agent/controllers/agent_controller.dart | 92 ++++++++++++--- lib/modules/agent/views/message_bubble.dart | 6 +- .../views/message_bubble_navigation.dart | 71 ++++++++++-- .../bindings/navigation_binding.dart | 9 ++ .../controllers/navigation_controller.dart | 5 + .../navigation/views/navigation_view.dart | 17 +++ lib/routes/app_pages.dart | 8 ++ lib/routes/app_routes.dart | 1 + .../amap_service/AmapServicePlugin.kt | 103 ++++++++++++++++- .../amap_service/NavigationService.kt | 97 +++++++++++++++- .../src/main/res/drawable/ic_navigation.xml | 8 +- .../android/src/main/res/values/colors.xml | 15 +++ .../amap_service/lib/amap_service.dart | 107 ++++++++++++------ 15 files changed, 473 insertions(+), 77 deletions(-) create mode 100644 lib/modules/navigation/bindings/navigation_binding.dart create mode 100644 lib/modules/navigation/controllers/navigation_controller.dart create mode 100644 lib/modules/navigation/views/navigation_view.dart create mode 100644 local_plugins/amap_service/android/src/main/res/values/colors.xml 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 {} From f678d8258fc358de6c6a3b48a0853219416b4d5b Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 21 May 2025 16:28:07 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=AB=98=E5=BE=B7?= =?UTF-8?q?=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/navigation_manager.dart | 6 +- .../agent/controllers/agent_controller.dart | 4 +- lib/modules/agent/views/message_bubble.dart | 145 +++++- .../views/message_bubble_navigation.dart | 145 ------ .../agent_service/AgentService.kt | 8 +- .../amap_service/android/build.gradle | 4 + .../android/src/main/AndroidManifest.xml | 5 + .../amap_service/AmapServicePlugin.kt | 114 +---- .../amap_service/NavigationActivity.kt | 254 ++++++++++ .../amap_service/NavigationService.kt | 479 +++++++++--------- ...eStarter.kt => NavigationServiceHelper.kt} | 47 +- .../main/res/layout/activity_navigation.xml | 10 + .../android/src/main/res/values/colors.xml | 5 + .../android/src/main/res/values/styles.xml | 9 + .../amap_service/lib/amap_controller.dart | 174 ------- .../amap_service/lib/amap_service.dart | 12 +- .../amap_service/lib/amap_service_map.dart | 20 - .../amap_service/lib/amap_widget.dart | 425 ---------------- .../lib/core/amap_flutter_platform.dart | 41 -- .../amap_service/lib/core/map_event.dart | 77 --- .../core/method_channel_amap_flutter_map.dart | 274 ---------- .../amap_service/lib/types/base_overlay.dart | 29 -- .../amap_service/lib/types/bitmap.dart | 140 ----- .../amap_service/lib/types/camera.dart | 150 ------ .../amap_service/lib/types/marker.dart | 249 --------- .../lib/types/marker_updates.dart | 105 ---- .../amap_service/lib/types/polygon.dart | 120 ----- .../lib/types/polygon_updates.dart | 108 ---- .../amap_service/lib/types/polyline.dart | 198 -------- .../lib/types/polyline_updates.dart | 108 ---- .../amap_service/lib/types/types.dart | 11 - local_plugins/amap_service/lib/types/ui.dart | 257 ---------- local_plugins/amap_service/pubspec.yaml | 2 - .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/cache.properties | 4 +- 35 files changed, 725 insertions(+), 3014 deletions(-) delete mode 100644 lib/modules/agent/views/message_bubble_navigation.dart create mode 100644 local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt rename local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/{NavigationServiceStarter.kt => NavigationServiceHelper.kt} (67%) create mode 100644 local_plugins/amap_service/android/src/main/res/layout/activity_navigation.xml create mode 100644 local_plugins/amap_service/android/src/main/res/values/styles.xml delete mode 100644 local_plugins/amap_service/lib/amap_controller.dart delete mode 100644 local_plugins/amap_service/lib/amap_service_map.dart delete mode 100644 local_plugins/amap_service/lib/amap_widget.dart delete mode 100644 local_plugins/amap_service/lib/core/amap_flutter_platform.dart delete mode 100644 local_plugins/amap_service/lib/core/map_event.dart delete mode 100644 local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart delete mode 100644 local_plugins/amap_service/lib/types/base_overlay.dart delete mode 100644 local_plugins/amap_service/lib/types/bitmap.dart delete mode 100644 local_plugins/amap_service/lib/types/camera.dart delete mode 100644 local_plugins/amap_service/lib/types/marker.dart delete mode 100644 local_plugins/amap_service/lib/types/marker_updates.dart delete mode 100644 local_plugins/amap_service/lib/types/polygon.dart delete mode 100644 local_plugins/amap_service/lib/types/polygon_updates.dart delete mode 100644 local_plugins/amap_service/lib/types/polyline.dart delete mode 100644 local_plugins/amap_service/lib/types/polyline_updates.dart delete mode 100644 local_plugins/amap_service/lib/types/types.dart delete mode 100644 local_plugins/amap_service/lib/types/ui.dart diff --git a/lib/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index d5da0ef00..924ef0877 100644 --- a/lib/data/services/navigation_manager.dart +++ b/lib/data/services/navigation_manager.dart @@ -27,7 +27,11 @@ class NavigationManager extends GetxService { } //导航 - Future navigation(String org, String dis) async { + Future start(String org, String dis) async { // await _amapervice.navigation(org, dis); } + //导航 + Future open() async { + await _amapervice.open(); + } } diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 33e76272d..6e81b5ce7 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -849,7 +849,7 @@ class AgentController extends GetxController { } //去导航 - void gotoNavigation() { - Get.toNamed(Routes.navigation); + void openNavigation() { + _navigationManager.open(); } } diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index ecce875e8..e2cae94cd 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -1,5 +1,6 @@ +import 'dart:convert'; + import 'package:common_utils/common_utils.dart'; -import 'package:deep_voice/modules/agent/views/message_bubble_navigation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; @@ -1171,6 +1172,148 @@ class MessageBubble extends GetView { // 默认或未知天气 return Icons.help_outline; } + + Widget navigationCard( + BuildContext context, { + required List> modes, + Map? navigation, + double height = 300, + }) { + 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( + 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: () { + setInnerState(() => currentIndex = index); + }, + child: SizedBox( + width: 50, + child: Text( + modes[index]["ntype"] as String, + textAlign: TextAlign.center, + style: TextStyle( + color: isSelected ? Colors.blue : Colors.black87, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + }), + ), + ), + + // 距离和耗时 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('距离:${modes[currentIndex]["distance"]}', + style: + const TextStyle(fontSize: 12, color: Colors.blue)), + IconButton( + icon: const Icon(Icons.turn_left, + color: Colors.blue, size: 20), + onPressed: () { + controller.openNavigation(); + }, + ), + ], + ), + ), + + // 导航实时数据显示 + 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/agent/views/message_bubble_navigation.dart b/lib/modules/agent/views/message_bubble_navigation.dart deleted file mode 100644 index 129fd8f32..000000000 --- a/lib/modules/agent/views/message_bubble_navigation.dart +++ /dev/null @@ -1,145 +0,0 @@ -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, - Map? navigation, - double height = 300, -}) { - 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( - 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: () { - setInnerState(() => currentIndex = index); - }, - child: SizedBox( - width: 50, - child: Text( - modes[index]["ntype"] as String, - textAlign: TextAlign.center, - style: TextStyle( - color: isSelected ? Colors.blue : Colors.black87, - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - ), - ); - }), - ), - ), - - // 距离和耗时 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('距离:${modes[currentIndex]["distance"]}', - style: const TextStyle(fontSize: 12, color: Colors.blue)), - 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/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 38f3ab2b3..574237a89 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,17 +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.NavigationServiceStarter +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 @@ -1190,7 +1186,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, 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..019b115bd 100644 --- a/local_plugins/amap_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/amap_service/android/src/main/AndroidManifest.xml @@ -39,6 +39,11 @@ 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,startpos,endpos,ntype) result.success(null) } else { result.error("INVALID_ARGUMENT", "Missing 'song' map", null) } } + "open" -> { + try { + NavigationServiceHelper.openNavigationUI() + }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.stopNavigation() result.success(null) - } else { - result.error("INVALID_ARGUMENT", "Missing or empty 'songs' list", null) - } } - else -> result.notImplemented() + else -> { + result.notImplemented() + } } } @@ -99,84 +94,5 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHan 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/NavigationActivity.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt new file mode 100644 index 000000000..5ba631c52 --- /dev/null +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationActivity.kt @@ -0,0 +1,254 @@ +package com.yunqiinnovation.amap_service + +import android.os.Bundle +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.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 + +class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewListener { + private lateinit var naviView: AMapNaviView + private var navi: AMapNavi? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_navigation) + + // 初始化导航视图 + naviView = findViewById(R.id.navi_view) + naviView.onCreate(savedInstanceState) + naviView.setAMapNaviViewListener(this) + + navi = NavigationService.sharedNavi + if (navi == null) { + finish() // 服务未启动,退出 + return + } + navi?.addAMapNaviListener(this); + + // 绑定当前 activity 到导航状态 + } + + override fun onResume() { + super.onResume() + naviView.onResume() + } + + override fun onPause() { + super.onPause() + naviView.onPause() + } + + override fun onDestroy() { + super.onDestroy() + naviView.onDestroy() + navi?.removeAMapNaviListener(this); + } + + override fun onInitNaviFailure() { +// TODO("Not yet implemented") + } + + override fun onInitNaviSuccess() { +// 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?) { +// 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() { +// TODO("Not yet implemented") + } + + override fun onNaviBackClick(): Boolean { +// TODO("Not yet implemented") + return true + } + + 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 9096b8b10..7342a0c4d 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 @@ -6,17 +6,14 @@ 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 import com.amap.api.navi.enums.PathPlanningStrategy import com.amap.api.navi.model.AMapCalcRouteResult import com.amap.api.navi.model.AMapLaneInfo @@ -36,7 +33,7 @@ import java.io.FileNotFoundException import java.util.concurrent.atomic.AtomicBoolean -class NavigationService: Service() { +class NavigationService: Service(),AMapNaviListener { companion object { private const val TAG = "NavigationService" @@ -48,53 +45,48 @@ 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" -> { - 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"); - Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") - // 初始化导航 - aMapNavi = AMapNavi.getInstance(applicationContext) - - aMapNavi.setUseInnerVoice(true) - aMapNavi.setBroadcastMode(1) - aMapNavi.addAMapNaviListener(naviListener) - }catch (e: FileNotFoundException){ - Log.w("NavigationService", "Custom voice bin not found: ${e.message}") - } - } - "stop" -> { - stopNavigation(); + try { + startpos = intent.getStringExtra("startpos").toString() + endpos = intent.getStringExtra("endpos").toString() + naviType = intent.getStringExtra("naviType"); + Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") + 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}") } 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( @@ -151,251 +143,250 @@ class NavigationService: Service() { val byteArray = outputStream.toByteArray() return Base64.encodeToString(byteArray, Base64.NO_WRAP) } - // 实现监听器,处理导航播报等 - private val naviListener = object : AMapNaviListener { - override fun onInitNaviFailure() { - Log.e(TAG, "onInitNaviFailure!") -// 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 = aMapNavi.calculateDriveRoute(startList, endList, null, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT); - if (!success) { - Log.e(TAG, "路径规划请求失败") - } + private fun stopNavigation() { + if (sharedNavi != null) { + sharedNavi?.stopNavi() + sharedNavi?.removeAMapNaviListener(this) } + isRunning.set(false) + stopSelf() + } - override fun onStartNavi(p0: Int) { - Log.e(TAG, "onStartNavi!") -// TODO("Not yet implemented") - } + override fun onBind(intent: Intent?): IBinder? = null - override fun onTrafficStatusUpdate() { - Log.e(TAG, "onTrafficStatusUpdate!") -// TODO("Not yet implemented") - } - override fun onLocationChange(location: AMapNaviLocation) { - Log.e(TAG, "onLocationChange! ${location}") - // 可通过广播发送位置信息到 Flutter - } + override fun onInitNaviFailure() { + Log.e(TAG, "onInitNaviFailure!") +// TODO("Not yet implemented") + } - override fun onGetNavigationText(p0: Int, p1: String?) { - Log.e(TAG, "onGetNavigationText! $p0 $p1") -// 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 onGetNavigationText(p0: String?) { - Log.e(TAG, "onGetNavigationText! $p0") -// TODO("Not yet implemented") - } + override fun onStartNavi(p0: Int) { + Log.e(TAG, "onStartNavi! $p0") +// TODO("Not yet implemented") + } - override fun onEndEmulatorNavi() { -// Log.e(TAG, "onEndEmulatorNavi! ") - Log.e(TAG, "onEndEmulatorNavi!") - stopNavigation() -// TODO("Not yet implemented") - } + override fun onTrafficStatusUpdate() { + Log.e(TAG, "onTrafficStatusUpdate!") +// TODO("Not yet implemented") + } - override fun onArriveDestination() { - Log.e(TAG, "onArriveDestination!") - stopNavigation() -// TODO("Not yet implemented") - } + override fun onLocationChange(p0: AMapNaviLocation?) { + Log.e(TAG, "onLocationChange! $p0") +// TODO("Not yet implemented") + } - override fun onCalculateRouteFailure(p0: Int) { - Log.e(TAG, "路径规划失败, 错误码: $p0") - // TODO: 可添加反馈机制给 Flutter 层或用户提示 - } + override fun onGetNavigationText(p0: Int, p1: String?) { + Log.e(TAG, "onGetNavigationText! $p0 $p1") +// TODO("Not yet implemented") + } - override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) { - Log.e(TAG, "onCalculateRouteFailure! ${p0}") -// TODO("Not yet implemented") - } + override fun onGetNavigationText(p0: String?) { + Log.e(TAG, "onGetNavigationText! $p0") +// TODO("Not yet implemented") + } - override fun onReCalculateRouteForYaw() { - Log.e(TAG, "onReCalculateRouteForYaw!") -// TODO("Not yet implemented") - } + override fun onEndEmulatorNavi() { + Log.e(TAG, "onEndEmulatorNavi!") + stopNavigation() +// TODO("Not yet implemented") + } - override fun onReCalculateRouteForTrafficJam() { - Log.e(TAG, "onReCalculateRouteForTrafficJam!") -// TODO("Not yet implemented") - } + override fun onArriveDestination() { + Log.e(TAG, "onArriveDestination!") + stopNavigation() +// TODO("Not yet implemented") + } - override fun onArrivedWayPoint(p0: Int) { - Log.e(TAG, "onArrivedWayPoint!") -// TODO("Not yet implemented") - } + override fun onCalculateRouteFailure(p0: Int) { + Log.e(TAG, "onCalculateRouteFailure! $p0") +// TODO("Not yet implemented") + } - override fun onGpsOpenStatus(p0: Boolean) { - 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 - - 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") - } + override fun onCalculateRouteFailure(p0: AMapCalcRouteResult?) { + 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 onReCalculateRouteForYaw() { + Log.e(TAG, "onReCalculateRouteForYaw!") +// TODO("Not yet implemented") + } - override fun updateIntervalCameraInfo( - p0: AMapNaviCameraInfo?, - p1: AMapNaviCameraInfo?, - p2: Int - ) { - Log.e(TAG, "updateIntervalCameraInfo! $p0") -// TODO("Not yet implemented") - } + override fun onReCalculateRouteForTrafficJam() { + Log.e(TAG, "onReCalculateRouteForTrafficJam!") +// TODO("Not yet implemented") + } - override fun onServiceAreaUpdate(p0: Array?) { - Log.e(TAG, "onServiceAreaUpdate! $p0") -// TODO("Not yet implemented") - } + override fun onArrivedWayPoint(p0: Int) { + Log.e(TAG, "onArrivedWayPoint! $p0") +// TODO("Not yet implemented") + } - override fun showCross(p0: AMapNaviCross?) { - Log.e(TAG, "showCross! $p0") -// TODO("Not yet implemented") - } + override fun onGpsOpenStatus(p0: Boolean) { + Log.e(TAG, "onGpsOpenStatus! $p0") +// TODO("Not yet implemented") + } - override fun hideCross() { - Log.e(TAG, "hideCross!") -// 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 showModeCross(p0: AMapModelCross?) { - Log.e(TAG, "showModeCross! $p0") -// TODO("Not yet implemented") - } + override fun updateCameraInfo(p0: Array?) { + Log.e(TAG, "updateCameraInfo! $p0") +// TODO("Not yet implemented") + } - override fun hideModeCross() { - Log.e(TAG, "hideModeCross!") -// 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: Array?, p1: ByteArray?, p2: ByteArray?) { - 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 showLaneInfo(p0: AMapLaneInfo?) { - Log.e(TAG, "showLaneInfo! $p0") -// TODO("Not yet implemented") - } + override fun showCross(p0: AMapNaviCross?) { + Log.e(TAG, "showCross!") +// TODO("Not yet implemented") + } - override fun hideLaneInfo() { - Log.e(TAG, "hideLaneInfo!") -// TODO("Not yet implemented") - } + override fun hideCross() { + Log.e(TAG, "hideCross!") +// TODO("Not yet implemented") + } - override fun onCalculateRouteSuccess(ids: IntArray?) { - Log.e(TAG, "onCalculateRouteSuccess! $ids") - // AMapNavi.getInstance(this).startNavi(1); - aMapNavi.startNavi(2) - } + override fun showModeCross(p0: AMapModelCross?) { + Log.e(TAG, "showModeCross! $p0") +// TODO("Not yet implemented") + } - override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { - Log.e(TAG, "onCalculateRouteSuccess! $p0") -// TODO("Not yet implemented") - } + override fun hideModeCross() { + Log.e(TAG, "hideModeCross!") +// TODO("Not yet implemented") + } - override fun notifyParallelRoad(p0: Int) { - Log.d(TAG, "是否在拥挤平行路段: $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: Array?) { - 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 OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) { - Log.d(TAG, "OnUpdateTrafficFacility: $p0") -// TODO("Not yet implemented") - } + override fun hideLaneInfo() { + Log.e(TAG, "hideLaneInfo!") +// TODO("Not yet implemented") + } - override fun updateAimlessModeStatistics(p0: AimLessModeStat?) { -// TODO("Not yet implemented") - Log.d(TAG, "updateAimlessModeStatistics: $p0") - } + override fun onCalculateRouteSuccess(p0: IntArray?) { + Log.e(TAG, "onCalculateRouteSuccess! $p0") + sharedNavi?.startNavi(2); +// TODO("Not yet implemented") + } - override fun updateAimlessModeCongestionInfo(p0: AimLessModeCongestionInfo?) { -// TODO("Not yet implemented") - Log.d(TAG, "updateAimlessModeCongestionInfo: $p0") - } + override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) { + sharedNavi?.startNavi(2); + Log.e(TAG, "onCalculateRouteSuccess! $p0") +// TODO("Not yet implemented") + } - override fun onPlayRing(p0: Int) { - Log.d(TAG, "onPlayRing: $p0") -// TODO("Not yet implemented") - } + override fun notifyParallelRoad(p0: Int) { + Log.e(TAG, "notifyParallelRoad! $p0") +// TODO("Not yet implemented") + } - override fun onNaviRouteNotify(p0: AMapNaviRouteNotifyData?) { - Log.w(TAG, "GPS信号弱") -// TODO("Not yet implemented") - } + override fun OnUpdateTrafficFacility(p0: Array?) { + Log.e(TAG, "OnUpdateTrafficFacility! $p0") +// TODO("Not yet implemented") + } - override fun onGpsSignalWeak(p0: Boolean) { - Log.w(TAG, "GPS信号弱") -// TODO("Not yet implemented") - } + override fun OnUpdateTrafficFacility(p0: AMapNaviTrafficFacilityInfo?) { + Log.e(TAG, "OnUpdateTrafficFacility! $p0") +// TODO("Not yet implemented") } - private fun stopNavigation() { - if (::aMapNavi.isInitialized) { - aMapNavi.stopNavi() - aMapNavi.removeAMapNaviListener(naviListener) - } - isRunning.set(false) - stopSelf() + override fun updateAimlessModeStatistics(p0: AimLessModeStat?) { + Log.e(TAG, "updateAimlessModeStatistics! $p0") +// TODO("Not yet implemented") } - override fun onBind(intent: Intent?): IBinder? = null + 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/NavigationServiceStarter.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt similarity index 67% rename from local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt rename to local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceHelper.kt index 980edb166..34575ed62 100644 --- 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/NavigationServiceHelper.kt @@ -6,7 +6,6 @@ import android.os.Handler import android.os.Looper import android.util.Log import androidx.core.content.ContextCompat -import com.yunqiinnovation.amap_service.NavigationService.Companion import java.util.Collections /** @@ -22,34 +21,34 @@ interface NavigationListener { fun onEvent(eventName: String, data: Map) } -object NavigationServiceStarter { +object NavigationServiceHelper { private val TAG = "NavigationServiceStarter" + private lateinit var context: Context private val listeners = Collections.synchronizedSet(mutableSetOf()) - //播放单曲 - fun startServiceWithCommand(context: Context, command: String, startpos: String? = null, endpos: String? = null, ntype: String? = null) { - Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $startpos $endpos $ntype") + fun init(appContext: Context) { + context = appContext + } + //启动导航 + fun startNavigation(context: Context, startpos: String? = null, endpos: String? = null, ntype: String? = null) { + Log.i(TAG, "liwei------------------- startNavigation: $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 - } + putExtra("startpos", startpos) + putExtra("endpos", endpos) + putExtra("ntype", ntype) } - 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") + context.startForegroundService(intent) + } + //停止导航 + fun stopNavigation() { + val intent = Intent(context, NavigationService::class.java) + context.stopService(intent) + } + //打开导航页面 + fun openNavigationUI() { + val intent = Intent(context, NavigationActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) } - /** * 添加事件监听器 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 index 3c6f394b8..f87690ccd 100644 --- a/local_plugins/amap_service/android/src/main/res/values/colors.xml +++ b/local_plugins/amap_service/android/src/main/res/values/colors.xml @@ -12,4 +12,9 @@ #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_controller.dart b/local_plugins/amap_service/lib/amap_controller.dart deleted file mode 100644 index 48db2802d..000000000 --- a/local_plugins/amap_service/lib/amap_controller.dart +++ /dev/null @@ -1,174 +0,0 @@ -part of amap_service_map; - -final MethodChannelAMapFlutterMap _methodChannel = - AMapFlutterPlatform.instance as MethodChannelAMapFlutterMap; - -/// 地图通信中心 -class AMapController { - final int mapId; - final _MapState _mapState; - - AMapController._( - CameraPosition initCameraPosition, - this._mapState, { - required this.mapId, - }) { - _connectStreams(mapId); - } - - ///根据传入的id初始化[AMapController] - /// 主要用于在[AMapWidget]初始化时在[AMapWidget.onMapCreated]中初始化controller - static Future init( - int id, - CameraPosition initialCameration, - _MapState mapState, - ) async { - await _methodChannel.init(id); - return AMapController._(initialCameration, mapState, mapId: id); - } - - ///只用于测试 - ///用于与native的通信 - @visibleForTesting - MethodChannel get channel { - return _methodChannel.channel(mapId); - } - - void _connectStreams(int mapId) { - if (_mapState.widget.onLocationChanged != null) { - _methodChannel - .onLocationChanged(mapId: mapId) - .listen( - (LocationChangedEvent e) => - _mapState.widget.onLocationChanged!(e.value), - ); - } - - if (_mapState.widget.onCameraMove != null) { - _methodChannel - .onCameraMove(mapId: mapId) - .listen( - (CameraPositionMoveEvent e) => - _mapState.widget.onCameraMove!(e.value), - ); - } - if (_mapState.widget.onCameraMoveEnd != null) { - _methodChannel - .onCameraMoveEnd(mapId: mapId) - .listen( - (CameraPositionMoveEndEvent e) => - _mapState.widget.onCameraMoveEnd!(e.value), - ); - } - if (_mapState.widget.onTap != null) { - _methodChannel - .onMapTap(mapId: mapId) - .listen(((MapTapEvent e) => _mapState.widget.onTap!(e.value))); - } - if (_mapState.widget.onLongPress != null) { - _methodChannel - .onMapLongPress(mapId: mapId) - .listen( - ((MapLongPressEvent e) => _mapState.widget.onLongPress!(e.value)), - ); - } - - if (_mapState.widget.onPoiTouched != null) { - _methodChannel - .onPoiTouched(mapId: mapId) - .listen( - ((MapPoiTouchEvent e) => _mapState.widget.onPoiTouched!(e.value)), - ); - } - - _methodChannel - .onMarkerTap(mapId: mapId) - .listen((MarkerTapEvent e) => _mapState.onMarkerTap(e.value)); - - _methodChannel - .onMarkerDragEnd(mapId: mapId) - .listen( - (MarkerDragEndEvent e) => - _mapState.onMarkerDragEnd(e.value, e.position), - ); - - _methodChannel - .onPolylineTap(mapId: mapId) - .listen((PolylineTapEvent e) => _mapState.onPolylineTap(e.value)); - } - - void disponse() { - _methodChannel.dispose(id: mapId); - } - - Future _updateMapOptions(Map optionsUpdate) { - return _methodChannel.updateMapOptions(optionsUpdate, mapId: mapId); - } - - Future _updateMarkers(MarkerUpdates markerUpdates) { - return _methodChannel.updateMarkers(markerUpdates, mapId: mapId); - } - - Future _updatePolylines(PolylineUpdates polylineUpdates) { - return _methodChannel.updatePolylines(polylineUpdates, mapId: mapId); - } - - Future _updatePolygons(PolygonUpdates polygonUpdates) { - return _methodChannel.updatePolygons(polygonUpdates, mapId: mapId); - } - - ///改变地图视角 - /// - ///通过[CameraUpdate]对象设置新的中心点、缩放比例、放大缩小、显示区域等内容 - /// - ///(注意:iOS端设置显示区域时,不支持duration参数,动画时长使用iOS地图默认值350毫秒) - /// - ///可选属性[animated]用于控制是否执行动画移动 - /// - ///可选属性[duration]用于控制执行动画的时长,默认250毫秒,单位:毫秒 - Future moveCamera( - CameraUpdate cameraUpdate, { - bool animated = true, - int duration = 250, - }) { - return _methodChannel.moveCamera( - cameraUpdate, - mapId: mapId, - animated: animated, - duration: duration, - ); - } - - ///设置地图每秒渲染的帧数 - Future setRenderFps(int fps) { - return _methodChannel.setRenderFps(fps, mapId: mapId); - } - - ///地图截屏 - Future takeSnapshot() { - return _methodChannel.takeSnapshot(mapId: mapId); - } - - /// 获取地图审图号(普通地图) - /// - /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 - /// - /// 如高德地图在"关于"中体现 - Future getMapContentApprovalNumber() { - return _methodChannel.getMapContentApprovalNumber(mapId: mapId); - } - - /// 获取地图审图号(卫星地图) - /// - /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 - /// - /// 如高德地图在"关于"中体现 - Future getSatelliteImageApprovalNumber() { - return _methodChannel.getSatelliteImageApprovalNumber(mapId: mapId); - } - - /// 清空缓存 - Future clearDisk() { - return _methodChannel.clearDisk(mapId: mapId); - } -} diff --git a/local_plugins/amap_service/lib/amap_service.dart b/local_plugins/amap_service/lib/amap_service.dart index 703030e3f..29bc9d921 100644 --- a/local_plugins/amap_service/lib/amap_service.dart +++ b/local_plugins/amap_service/lib/amap_service.dart @@ -94,8 +94,16 @@ class AmapService { } } - Future show() async { - await _methodChannel.invokeMethod('show'); + Future start() async { + await _methodChannel.invokeMethod('start'); + } + + Future open() async { + await _methodChannel.invokeMethod('open'); + } + + Future stop() async { + await _methodChannel.invokeMethod('stop'); } // 销毁 diff --git a/local_plugins/amap_service/lib/amap_service_map.dart b/local_plugins/amap_service/lib/amap_service_map.dart deleted file mode 100644 index d8404d6d7..000000000 --- a/local_plugins/amap_service/lib/amap_service_map.dart +++ /dev/null @@ -1,20 +0,0 @@ -/// 高德地图Flutter插件入口文件 -library amap_service_map; - -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; -import 'package:amap_flutter_map/src/core/map_event.dart'; -import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; -import 'package:amap_flutter_map/src/types/types.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -export 'package:amap_flutter_map/src/types/types.dart'; - -part 'amap_controller.dart'; -part 'amap_widget.dart'; diff --git a/local_plugins/amap_service/lib/amap_widget.dart b/local_plugins/amap_service/lib/amap_widget.dart deleted file mode 100644 index 90c7b4407..000000000 --- a/local_plugins/amap_service/lib/amap_widget.dart +++ /dev/null @@ -1,425 +0,0 @@ -part of amap_service_map; - -typedef void MapCreatedCallback(AMapController controller); - -///用于展示高德地图的Widget -class AMapWidget extends StatefulWidget { - ///高德开放平台的key - /// - final AMapApiKey? apiKey; - - /// 初始化时的地图中心点 - final CameraPosition initialCameraPosition; - - ///地图类型 - final MapType mapType; - - ///自定义地图样式 - final CustomStyleOptions? customStyleOptions; - - ///定位小蓝点 - final MyLocationStyleOptions? myLocationStyleOptions; - - ///缩放级别范围 - final MinMaxZoomPreference? minMaxZoomPreference; - - ///地图显示范围 - final LatLngBounds? limitBounds; - - ///显示路况开关 - final bool trafficEnabled; - - /// 地图poi是否允许点击 - final bool touchPoiEnabled; - - ///是否显示3D建筑物 - final bool buildingsEnabled; - - ///是否显示底图文字标注 - final bool labelsEnabled; - - ///是否显示指南针 - final bool compassEnabled; - - ///是否显示比例尺 - final bool scaleEnabled; - - ///是否支持缩放手势 - final bool zoomGesturesEnabled; - - ///是否支持滑动手势 - final bool scrollGesturesEnabled; - - ///是否支持旋转手势 - final bool rotateGesturesEnabled; - - ///是否支持倾斜手势 - final bool tiltGesturesEnabled; - - /// 地图上显示的Marker - final Set markers; - - /// 地图上显示的polyline - final Set polylines; - - /// 地图上显示的polygon - final Set polygons; - - /// 地图创建成功的回调, 收到此回调之后才可以操作地图 - final MapCreatedCallback? onMapCreated; - - /// 相机视角持续移动的回调 - final ArgumentCallback? onCameraMove; - - /// 相机视角移动结束的回调 - final ArgumentCallback? onCameraMoveEnd; - - /// 地图单击事件的回调 - final ArgumentCallback? onTap; - - /// 地图长按事件的回调 - final ArgumentCallback? onLongPress; - - /// 地图POI的点击回调,需要`touchPoiEnabled`true,才能回调 - final ArgumentCallback? onPoiTouched; - - ///位置回调 - final ArgumentCallback? onLocationChanged; - - ///需要应用到地图上的手势集合 - final Set> gestureRecognizers; - - ///高德合规声明配置 - /// - /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy - final AMapPrivacyStatement? privacyStatement; - - /// 创建一个展示高德地图的widget - /// - /// 如果使用的高德地图SDK的版本是8.1.0及以上版本, - /// 在app首次启动时必须传入高德合规声明配置[privacyStatement],后续如果没有变化不需要重复设置 - ///
  • [privacyStatement.hasContains] 隐私权政策是否包含高德开平隐私权政策
  • - ///
  • [privacyStatement.hasShow] 是否已经弹窗展示给用户
  • - ///
  • [privacyStatement.hasAgree] 隐私权政策是否已经取得用户同意
  • - /// 以上三个值,任何一个为false都会造成地图插件不工作(白屏情况) - /// - /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy - /// - /// [AssertionError] will be thrown if [initialCameraPosition] is null; - const AMapWidget({ - Key? key, - this.privacyStatement, - this.apiKey, - this.initialCameraPosition = const CameraPosition( - target: LatLng(39.909187, 116.397451), - zoom: 10, - ), - this.mapType = MapType.normal, - this.buildingsEnabled = true, - this.compassEnabled = false, - this.labelsEnabled = true, - this.limitBounds, - this.minMaxZoomPreference, - this.rotateGesturesEnabled = true, - this.scaleEnabled = true, - this.scrollGesturesEnabled = true, - this.tiltGesturesEnabled = true, - this.touchPoiEnabled = true, - this.trafficEnabled = false, - this.zoomGesturesEnabled = true, - this.onMapCreated, - this.gestureRecognizers = const >{}, - this.customStyleOptions, - this.myLocationStyleOptions, - this.onCameraMove, - this.onCameraMoveEnd, - this.onLocationChanged, - this.onTap, - this.onLongPress, - this.onPoiTouched, - this.markers = const {}, - this.polylines = const {}, - this.polygons = const {}, - }) : super(key: key); - - /// - @override - State createState() => _MapState(); -} - -class _MapState extends State { - Map _markers = {}; - Map _polylines = {}; - Map _polygons = {}; - - final Completer _controller = Completer(); - late _AMapOptions _mapOptions; - - @override - Widget build(BuildContext context) { - AMapUtil.init(context); - final Map creationParams = { - 'privacyStatement': widget.privacyStatement?.toMap(), - 'apiKey': widget.apiKey?.toMap(), - 'initialCameraPosition': widget.initialCameraPosition.toMap(), - 'options': _mapOptions.toMap(), - 'markersToAdd': serializeOverlaySet(widget.markers), - 'polylinesToAdd': serializeOverlaySet(widget.polylines), - 'polygonsToAdd': serializeOverlaySet(widget.polygons), - }; - Widget mapView = _methodChannel.buildView( - creationParams, - widget.gestureRecognizers, - onPlatformViewCreated, - ); - return mapView; - } - - @override - void initState() { - super.initState(); - _mapOptions = _AMapOptions.fromWidget(widget); - _markers = keyByMarkerId(widget.markers); - _polygons = keyByPolygonId(widget.polygons); - _polylines = keyByPolylineId(widget.polylines); - print('initState AMapWidget'); - } - - @override - void dispose() async { - super.dispose(); - AMapController controller = await _controller.future; - controller.disponse(); - print('dispose AMapWidget with mapId: ${controller.mapId}'); - } - - @override - void reassemble() { - super.reassemble(); - print('reassemble AMapWidget'); - } - - @override - void deactivate() async { - super.deactivate(); - print('deactivate AMapWidget}'); - } - - @override - void didUpdateWidget(covariant AMapWidget oldWidget) { - super.didUpdateWidget(oldWidget); - _updateOptions(); - _updateMarkers(); - _updatePolylines(); - _updatePolygons(); - } - - Future onPlatformViewCreated(int id) async { - final AMapController controller = await AMapController.init( - id, - widget.initialCameraPosition, - this, - ); - _controller.complete(controller); - final MapCreatedCallback? _onMapCreated = widget.onMapCreated; - if (_onMapCreated != null) { - _onMapCreated(controller); - } - } - - void onMarkerTap(String markerId) { - final Marker? _marker = _markers[markerId]; - if (_marker != null) { - final ArgumentCallback? _onTap = _marker.onTap; - if (_onTap != null) { - _onTap(markerId); - } - } - } - - void onMarkerDragEnd(String markerId, LatLng position) { - final Marker? _marker = _markers[markerId]; - if (_marker != null) { - final MarkerDragEndCallback? _onDragEnd = _marker.onDragEnd; - if (_onDragEnd != null) { - _onDragEnd(markerId, position); - } - } - } - - void onPolylineTap(String polylineId) { - final Polyline? _polyline = _polylines[polylineId]; - if (_polyline != null) { - final ArgumentCallback? _onTap = _polyline.onTap; - if (_onTap != null) { - _onTap(polylineId); - } - } - } - - void _updateOptions() async { - final _AMapOptions newOptions = _AMapOptions.fromWidget(widget); - final Map updates = _mapOptions._updatesMap(newOptions); - if (updates.isEmpty) { - return; - } - final AMapController controller = await _controller.future; - // ignore: unawaited_futures - controller._updateMapOptions(updates); - _mapOptions = newOptions; - } - - void _updateMarkers() async { - final AMapController controller = await _controller.future; - // ignore: unawaited_futures - controller._updateMarkers( - MarkerUpdates.from(_markers.values.toSet(), widget.markers), - ); - _markers = keyByMarkerId(widget.markers); - } - - void _updatePolylines() async { - final AMapController controller = await _controller.future; - controller._updatePolylines( - PolylineUpdates.from(_polylines.values.toSet(), widget.polylines), - ); - _polylines = keyByPolylineId(widget.polylines); - } - - void _updatePolygons() async { - final AMapController controller = await _controller.future; - controller._updatePolygons( - PolygonUpdates.from(_polygons.values.toSet(), widget.polygons), - ); - _polygons = keyByPolygonId(widget.polygons); - } -} - -//高德地图参数设置 -class _AMapOptions { - ///地图类型 - final MapType mapType; - - ///自定义地图样式 - final CustomStyleOptions? customStyleOptions; - - ///定位小蓝点 - final MyLocationStyleOptions? myLocationStyleOptions; - - //缩放级别范围 - final MinMaxZoomPreference? minMaxZoomPreference; - - ///地图显示范围 - final LatLngBounds? limitBounds; - - ///显示路况开关 - final bool? trafficEnabled; - - /// 地图poi是否允许点击 - final bool? touchPoiEnabled; - - ///是否显示3D建筑物 - final bool? buildingsEnabled; - - ///是否显示底图文字标注 - final bool? labelsEnabled; - - ///是否显示指南针 - final bool? compassEnabled; - - ///是否显示比例尺 - final bool? scaleEnabled; - - ///是否支持缩放手势 - final bool? zoomGesturesEnabled; - - ///是否支持滑动手势 - final bool? scrollGesturesEnabled; - - ///是否支持旋转手势 - final bool? rotateGesturesEnabled; - - ///是否支持仰角手势 - final bool? tiltGesturesEnabled; - - _AMapOptions({ - this.mapType = MapType.normal, - this.buildingsEnabled, - this.customStyleOptions, - this.myLocationStyleOptions, - this.compassEnabled, - this.labelsEnabled, - this.limitBounds, - this.minMaxZoomPreference, - this.scaleEnabled, - this.touchPoiEnabled, - this.trafficEnabled, - this.rotateGesturesEnabled, - this.scrollGesturesEnabled, - this.tiltGesturesEnabled, - this.zoomGesturesEnabled, - }); - - static _AMapOptions fromWidget(AMapWidget map) { - return _AMapOptions( - mapType: map.mapType, - buildingsEnabled: map.buildingsEnabled, - compassEnabled: map.compassEnabled, - labelsEnabled: map.labelsEnabled, - limitBounds: map.limitBounds, - minMaxZoomPreference: map.minMaxZoomPreference, - scaleEnabled: map.scaleEnabled, - touchPoiEnabled: map.touchPoiEnabled, - trafficEnabled: map.trafficEnabled, - rotateGesturesEnabled: map.rotateGesturesEnabled, - scrollGesturesEnabled: map.scrollGesturesEnabled, - tiltGesturesEnabled: map.tiltGesturesEnabled, - zoomGesturesEnabled: map.zoomGesturesEnabled, - customStyleOptions: map.customStyleOptions?.clone(), - myLocationStyleOptions: map.myLocationStyleOptions?.clone(), - ); - } - - Map toMap() { - final Map optionsMap = {}; - void addIfNonNull(String fieldName, dynamic value) { - if (value != null) { - optionsMap[fieldName] = value; - } - } - - addIfNonNull('mapType', mapType.index); - addIfNonNull('buildingsEnabled', buildingsEnabled); - addIfNonNull('customStyleOptions', customStyleOptions?.clone().toMap()); - addIfNonNull('compassEnabled', compassEnabled); - addIfNonNull('labelsEnabled', labelsEnabled); - addIfNonNull('limitBounds', limitBounds?.toJson()); - addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?.toJson()); - addIfNonNull('scaleEnabled', scaleEnabled); - addIfNonNull('touchPoiEnabled', touchPoiEnabled); - addIfNonNull('trafficEnabled', trafficEnabled); - addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); - addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); - addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled); - addIfNonNull('zoomGesturesEnabled', zoomGesturesEnabled); - addIfNonNull('myLocationStyle', myLocationStyleOptions?.clone().toMap()); - return optionsMap; - } - - Map _updatesMap(_AMapOptions newOptions) { - final Map prevOptionsMap = toMap(); - - return newOptions.toMap()..removeWhere( - (String key, dynamic value) => - (_checkChange(key, prevOptionsMap[key], value)), - ); - } - - bool _checkChange(String key, dynamic preValue, dynamic newValue) { - if (key == 'myLocationStyle' || key == 'customStyleOptions') { - return preValue?.toString() == newValue?.toString(); - } else { - return preValue == newValue; - } - } -} diff --git a/local_plugins/amap_service/lib/core/amap_flutter_platform.dart b/local_plugins/amap_service/lib/core/amap_flutter_platform.dart deleted file mode 100644 index eaef547f3..000000000 --- a/local_plugins/amap_service/lib/core/amap_flutter_platform.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -/// “amap_flutter_map”平台特定实现必须扩展的接口 -abstract class AMapFlutterPlatform extends PlatformInterface { - static final Object _token = Object(); - AMapFlutterPlatform() : super(token: _token); - static AMapFlutterPlatform _instance = MethodChannelAMapFlutterMap() as AMapFlutterPlatform; - - /// The default instance of [AMapFlutterPlatform] to use. - /// - /// Defaults to [MethodChannelAMapFlutterMap]. - static AMapFlutterPlatform get instance => _instance; - - static set instance(AMapFlutterPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - /// /// Initializes the platform interface with [id]. - /// - /// This method is called when the plugin is first initialized. - Future init(int id) { - throw UnimplementedError('init() has not been implemented.'); - } - - void dispose({required int id}) { - throw UnimplementedError('dispose() has not been implemented.'); - } - - Widget buildView( - Map creationParams, - Set> gestureRecognizers, - PlatformViewCreatedCallback onPlatformViewCreated) { - throw UnimplementedError('buildView() has not been implemented.'); - } -} diff --git a/local_plugins/amap_service/lib/core/map_event.dart b/local_plugins/amap_service/lib/core/map_event.dart deleted file mode 100644 index 8e217b07c..000000000 --- a/local_plugins/amap_service/lib/core/map_event.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'package:amap_flutter_map/amap_flutter_map.dart'; - -///地图事件处理 -class MapEvent { - /// 地图id - final int mapId; - - ///返回的内容,对应的[MethodCall]中的[[arguments]] - final T value; - - /// 构造一个event - /// - /// `mapId` 当前地图的id - /// `value` 需要传输的值,可以为`null`. - MapEvent(this.mapId, this.value); -} - -///定位回调接口 -class LocationChangedEvent extends MapEvent { - LocationChangedEvent(int mapId, AMapLocation value) : super(mapId, value); -} - -///地图移动回调 -class CameraPositionMoveEvent extends MapEvent { - CameraPositionMoveEvent(int mapId, CameraPosition value) - : super(mapId, value); -} - -///地图移动结束回调 -class CameraPositionMoveEndEvent extends MapEvent { - CameraPositionMoveEndEvent(int mapId, CameraPosition value) - : super(mapId, value); -} - -///点击地图回调 -class MapTapEvent extends MapEvent { - MapTapEvent(int mapId, LatLng value) : super(mapId, value); -} - -///长按地图回调 -class MapLongPressEvent extends MapEvent { - MapLongPressEvent(int mapId, LatLng value) : super(mapId, value); -} - -/// 带位置回调的地图事件 -class _PositionedMapEvent extends MapEvent { - /// 事件中带的位置信息 - final LatLng position; - - /// 构造一个带位置的地图事件, - /// - /// `mapId` 当前地图的id - /// `value` 需要传输的值,可以为`null`. - _PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); -} - -/// [Marker] 的点击事件 -class MarkerTapEvent extends MapEvent { - MarkerTapEvent(int mapId, String markerId) : super(mapId, markerId); -} - -/// [Marker] 的拖拽结束事件,附带拖拽结束时的位置信息[LatLng]. -class MarkerDragEndEvent extends _PositionedMapEvent { - MarkerDragEndEvent(int mapId, LatLng position, String markerId) - : super(mapId, position, markerId); -} - -/// [Polyline] 的点击事件 -class PolylineTapEvent extends MapEvent { - PolylineTapEvent(int mapId, String polylineId) : super(mapId, polylineId); -} - -/// Poi点击事件 -class MapPoiTouchEvent extends MapEvent { - MapPoiTouchEvent(int mapId, AMapPoi poi) : super(mapId, poi); -} diff --git a/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart b/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart deleted file mode 100644 index 59c561c5f..000000000 --- a/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart +++ /dev/null @@ -1,274 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; -import 'package:amap_flutter_map/src/types/types.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; -import 'package:stream_transform/stream_transform.dart'; - -import 'map_event.dart'; - -const VIEW_TYPE = 'com.amap.flutter.map'; - -/// 使用[MethodChannel]与Native代码通信的[AMapFlutterPlatform]的实现。 -class MethodChannelAMapFlutterMap implements AMapFlutterPlatform { - final Map _channels = {}; - - MethodChannel channel(int mapId) { - return _channels[mapId]!; - } - - @override - Future init(int mapId) { - MethodChannel? channel = _channels[mapId]; - if (channel == null) { - channel = MethodChannel('amap_flutter_map_$mapId'); - channel.setMethodCallHandler((call) => _handleMethodCall(call, mapId)); - _channels[mapId] = channel; - } - return channel.invokeMethod('map#waitForMap'); - } - - ///更新地图参数 - Future updateMapOptions( - Map newOptions, { - required int mapId, - }) { - return channel(mapId).invokeMethod( - 'map#update', - { - 'options': newOptions, - }, - ); - } - - /// 更新Marker的数据 - Future updateMarkers( - MarkerUpdates markerUpdates, { - required int mapId, - }) { - return channel(mapId).invokeMethod( - 'markers#update', - markerUpdates.toMap(), - ); - } - - /// 更新polyline的数据 - Future updatePolylines( - PolylineUpdates polylineUpdates, { - required int mapId, - }) { - return channel(mapId).invokeMethod( - 'polylines#update', - polylineUpdates.toMap(), - ); - } - - /// 更新polygon的数据 - Future updatePolygons( - PolygonUpdates polygonUpdates, { - required int mapId, - }) { - return channel(mapId).invokeMethod( - 'polygons#update', - polygonUpdates.toMap(), - ); - } - - @override - void dispose({required int id}) { - if (_channels.containsKey(id)) { - _channels.remove(id); - } - } - - @override - Widget buildView( - Map creationParams, - Set> gestureRecognizers, - void Function(int id) onPlatformViewCreated) { - if (defaultTargetPlatform == TargetPlatform.android) { - creationParams['debugMode'] = kDebugMode; - return AndroidView( - viewType: VIEW_TYPE, - onPlatformViewCreated: onPlatformViewCreated, - gestureRecognizers: gestureRecognizers, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - return UiKitView( - viewType: VIEW_TYPE, - onPlatformViewCreated: onPlatformViewCreated, - gestureRecognizers: gestureRecognizers, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } - return Text('当前平台:$defaultTargetPlatform, 不支持使用高德地图插件'); - } - - // handleMethodCall的`broadcast` - final StreamController _mapEventStreamController = - StreamController.broadcast(); - - // 根据mapid返回相应的event. - Stream _events(int mapId) => - _mapEventStreamController.stream.where((event) => event.mapId == mapId); - - //定位回调 - Stream onLocationChanged({required int mapId}) { - return _events(mapId).whereType(); - } - - //Camera 移动回调 - Stream onCameraMove({required int mapId}) { - return _events(mapId).whereType(); - } - - ///Camera 移动结束回调 - Stream onCameraMoveEnd({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onMapTap({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onMapLongPress({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onPoiTouched({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onMarkerTap({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onMarkerDragEnd({required int mapId}) { - return _events(mapId).whereType(); - } - - Stream onPolylineTap({required int mapId}) { - return _events(mapId).whereType(); - } - - Future _handleMethodCall(MethodCall call, int mapId) async { - switch (call.method) { - case 'location#changed': - try { - _mapEventStreamController.add(LocationChangedEvent( - mapId, AMapLocation.fromMap(call.arguments['location'])!)); - } catch (e) { - print("location#changed error=======>" + e.toString()); - } - break; - - case 'camera#onMove': - try { - _mapEventStreamController.add(CameraPositionMoveEvent( - mapId, CameraPosition.fromMap(call.arguments['position'])!)); - } catch (e) { - print("camera#onMove error===>" + e.toString()); - } - break; - case 'camera#onMoveEnd': - try { - _mapEventStreamController.add(CameraPositionMoveEndEvent( - mapId, CameraPosition.fromMap(call.arguments['position'])!)); - } catch (e) { - print("camera#onMoveEnd error===>" + e.toString()); - } - break; - case 'map#onTap': - _mapEventStreamController - .add(MapTapEvent(mapId, LatLng.fromJson(call.arguments['latLng'])!)); - break; - case 'map#onLongPress': - _mapEventStreamController.add(MapLongPressEvent( - mapId, LatLng.fromJson(call.arguments['latLng'])!)); - break; - - case 'marker#onTap': - _mapEventStreamController.add(MarkerTapEvent( - mapId, - call.arguments['markerId'], - )); - break; - case 'marker#onDragEnd': - _mapEventStreamController.add(MarkerDragEndEvent( - mapId, - LatLng.fromJson(call.arguments['position'])!, - call.arguments['markerId'])); - break; - case 'polyline#onTap': - _mapEventStreamController - .add(PolylineTapEvent(mapId, call.arguments['polylineId'])); - break; - case 'map#onPoiTouched': - try { - _mapEventStreamController.add( - MapPoiTouchEvent(mapId, AMapPoi.fromJson(call.arguments['poi'])!)); - } catch (e) { - print('map#onPoiTouched error===>' + e.toString()); - } - break; - } - } - - ///移动镜头到一个新的位置 - Future moveCamera( - CameraUpdate cameraUpdate, { - required int mapId, - bool animated = true, - int duration = 0, - }) { - return channel(mapId).invokeMethod('camera#move', { - 'cameraUpdate': cameraUpdate.toJson(), - 'animated': animated, - 'duration': duration - }); - } - - ///设置地图每秒渲染的帧数 - Future setRenderFps(int fps, {required int mapId}) { - return channel(mapId) - .invokeMethod('map#setRenderFps', { - 'fps': fps, - }); - } - - ///截屏 - Future takeSnapshot({ - required int mapId, - }) { - return channel(mapId).invokeMethod('map#takeSnapshot'); - } - - //获取地图审图号(普通地图) - Future getMapContentApprovalNumber({ - required int mapId, - }) { - return channel(mapId).invokeMethod('map#contentApprovalNumber'); - } - - //获取地图审图号(卫星地图) - Future getSatelliteImageApprovalNumber({ - required int mapId, - }) { - return channel(mapId) - .invokeMethod('map#satelliteImageApprovalNumber'); - } - - Future clearDisk({ - required int mapId, - }) { - return channel(mapId).invokeMethod('map#clearDisk'); - } -} diff --git a/local_plugins/amap_service/lib/types/base_overlay.dart b/local_plugins/amap_service/lib/types/base_overlay.dart deleted file mode 100644 index f8dbf47f2..000000000 --- a/local_plugins/amap_service/lib/types/base_overlay.dart +++ /dev/null @@ -1,29 +0,0 @@ -/// 地图覆盖物基类 -class BaseOverlay { - /// overlay id - late String _id; - - String get id => _id; - - BaseOverlay() { - this._id = this.hashCode.toString(); - } - - void setIdForCopy(String copyId) => _id = copyId; - - BaseOverlay clone() { - throw UnimplementedError( - 'BaseOverlay subClass should implement this methed.'); - } - - Map toMap() { - throw UnimplementedError( - 'BaseOverlay subClass should implement this methed.'); - } -} - -List>? serializeOverlaySet(Set overlays) { - return overlays - .map>((BaseOverlay overlay) => overlay.toMap()) - .toList(); -} diff --git a/local_plugins/amap_service/lib/types/bitmap.dart b/local_plugins/amap_service/lib/types/bitmap.dart deleted file mode 100644 index 47e2579c4..000000000 --- a/local_plugins/amap_service/lib/types/bitmap.dart +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async' show Future; -import 'dart:typed_data' show Uint8List; -import 'dart:ui'; - -import 'package:amap_flutter_base/amap_flutter_base.dart' show AMapUtil; -import 'package:flutter/material.dart' - show ImageConfiguration, AssetImage, AssetBundleImageKey; -import 'package:flutter/services.dart' show AssetBundle; - -import 'package:flutter/foundation.dart' show kIsWeb; - -/// Bitmap工具类 -class BitmapDescriptor { - const BitmapDescriptor._(this._json); - - /// 红色. - static const double hueRed = 0.0; - - /// 橙色. - static const double hueOrange = 30.0; - - /// 黄色. - static const double hueYellow = 60.0; - - /// 绿色. - static const double hueGreen = 120.0; - - /// 青色. - static const double hueCyan = 180.0; - - /// 天蓝色. - static const double hueAzure = 210.0; - - /// 蓝色. - static const double hueBlue = 240.0; - - /// 紫色. - static const double hueViolet = 270.0; - - /// 酒红色. - static const double hueMagenta = 300.0; - - /// 玫瑰红. - static const double hueRose = 330.0; - - /// 创建默认的marker 图标的 bitmap 描述信息对象. - static const BitmapDescriptor defaultMarker = - BitmapDescriptor._(['defaultMarker']); - - /// 创建引用默认着色的BitmapDescriptor - static BitmapDescriptor defaultMarkerWithHue(double hue) { - assert(0.0 <= hue && hue < 360.0); - String filename = "BLUE.png"; - if (hue == hueRed) { - filename = "RED.png"; - } else if (hue == hueOrange) { - filename = "ORANGE.png"; - } else if (hue == hueYellow) { - filename = "YELLOW.png"; - } else if (hue == hueGreen) { - filename = "GREEN.png"; - } else if (hue == hueCyan) { - filename = "CYAN.png"; - } else if (hue == hueAzure) { - filename = "AZURE.png"; - } else if (hue == hueBlue) { - filename = "BLUE.png"; - } else if (hue == hueViolet) { - filename = "VIOLET.png"; - } else if (hue == hueMagenta) { - filename = "MAGENTA.png"; - } else if (hue == hueRose) { - filename = "ROSE.png"; - } - return BitmapDescriptor._([ - 'fromAssetImage', - "packages/amap_flutter_map/res/$filename", - AMapUtil.devicePixelRatio - ]); - } - - ///根据输入的icon路径[iconPath]创建[BitmapDescriptor] - static BitmapDescriptor fromIconPath(String iconPath) { - return BitmapDescriptor._([ - 'fromAsset', - iconPath, - ]); - } - - ///从资源图像创建[BitmapDescriptor]。 - /// - ///Flutter中的assert的资产图像按以下方式存储: - /// https://flutter.dev/docs/development/ui/assets and images声明-分辨率感知图像资源 - ///该方法考虑了各种资产解决方案 - ///并根据dpi将图像缩放到正确的分辨率。 - ///将`mipmaps1设置为false可加载图像的精确dpi版本,默认情况下,`mipmap`为true。 - static Future fromAssetImage( - ImageConfiguration configuration, - String assetName, { - AssetBundle? bundle, - String? package, - bool mipmaps = true, - }) async { - if (!mipmaps && configuration.devicePixelRatio != null) { - return BitmapDescriptor._([ - 'fromAssetImage', - assetName, - configuration.devicePixelRatio, - ]); - } - final AssetImage assetImage = - AssetImage(assetName, package: package, bundle: bundle); - final AssetBundleImageKey assetBundleImageKey = - await assetImage.obtainKey(configuration); - final Size? size = configuration.size; - return BitmapDescriptor._([ - 'fromAssetImage', - assetBundleImageKey.name, - assetBundleImageKey.scale, - if (kIsWeb && size != null) - [ - size.width, - size.height, - ], - ]); - } - - /// 根据将PNG图片转换后的二进制数据[byteData]创建BitmapDescriptor - static BitmapDescriptor fromBytes(Uint8List byteData) { - return BitmapDescriptor._(['fromBytes', byteData]); - } - - final dynamic _json; - - dynamic toMap() => _json; -} diff --git a/local_plugins/amap_service/lib/types/camera.dart b/local_plugins/amap_service/lib/types/camera.dart deleted file mode 100644 index 2a4b5bc3c..000000000 --- a/local_plugins/amap_service/lib/types/camera.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'package:flutter/cupertino.dart'; - -/// 相机位置,包含可视区域的位置参数。 -class CameraPosition { - /// 构造一个CameraPosition 对象 - /// - /// 如果[bearing], [target], [tilt], 或者 [zoom] 为null时会返回[AssertionError] - const CameraPosition({ - this.bearing = 0.0, - required this.target, - this.tilt = 0.0, - this.zoom = 10, - }); - - /// 可视区域指向的方向,以角度为单位,从正北向逆时针方向计算,从0 度到360 度。 - final double bearing; - - /// 目标位置的屏幕中心点经纬度坐标。 - final LatLng target; - - /// 目标可视区域的倾斜度,以角度为单位。范围从0到360度 - final double tilt; - - /// 目标可视区域的缩放级别 - final double zoom; - - /// 将[CameraPosition]装换成Map - /// - /// 主要在插件内部使用 - dynamic toMap() => { - 'bearing': bearing, - 'target': target.toJson(), - 'tilt': tilt, - 'zoom': zoom, - }; - - /// 从Map转换成[CameraPosition] - /// - /// 主要在插件内部使用 - static CameraPosition? fromMap(dynamic json) { - if (json == null || !(json is Map)) { - return null; - } - final LatLng? target = LatLng.fromJson(json['target']); - if (target == null) { - return null; - } - return CameraPosition( - bearing: json['bearing'], - target: target, - tilt: json['tilt'], - zoom: json['zoom'], - ); - } - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; - final CameraPosition typedOther = other; - return bearing == typedOther.bearing && - target == typedOther.target && - tilt == typedOther.tilt && - zoom == typedOther.zoom; - } - - @override - int get hashCode => Object.hash(bearing, target, tilt, zoom); - - @override - String toString() => - 'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; -} - -/// 描述地图状态将要发生的变化 -class CameraUpdate { - CameraUpdate._(this._json); - - ///返回根据新的[CameraPosition]移动后的[CameraUpdate]. - /// - ///主要用于改变地图的中心点、缩放级别、倾斜角、角度等信息 - static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { - return CameraUpdate._([ - 'newCameraPosition', - cameraPosition.toMap(), - ]); - } - - ///移动到一个新的位置点[latLng] - /// - ///主要用于改变地图的中心点 - static CameraUpdate newLatLng(LatLng latLng) { - return CameraUpdate._(['newLatLng', latLng.toJson()]); - } - - ///根据指定到摄像头显示范围[bounds]和边界值[padding]创建一个CameraUpdate对象 - /// - ///主要用于根据指定的显示范围[bounds]以最佳的视野显示地图 - static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { - return CameraUpdate._([ - 'newLatLngBounds', - bounds.toJson(), - padding, - ]); - } - - /// 根据指定的新的位置[latLng]和缩放级别[zoom]创建一个CameraUpdate对象 - /// - /// 主要用于同时改变中心点和缩放级别 - static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { - return CameraUpdate._(['newLatLngZoom', latLng.toJson(), zoom]); - } - - /// 按照指定到像素点[dx]和[dy]移动地图中心点 - /// - /// [dx]是水平移动的像素数。正值代表可视区域向右移动,负值代表可视区域向左移动 - /// - /// [dy]是垂直移动的像素数。正值代表可视区域向下移动,负值代表可视区域向上移动 - /// - /// 返回包含x,y方向上移动像素数的cameraUpdate对象。 - static CameraUpdate scrollBy(double dx, double dy) { - return CameraUpdate._(['scrollBy', dx, dy]); - } - - /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 - /// - ///主要用于放大地图缩放级别,在当前地图显示的级别基础上加1 - static CameraUpdate zoomIn() { - return CameraUpdate._(['zoomIn']); - } - - /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 - /// - /// 主要用于减少地图缩放级别,在当前地图显示的级别基础上减1 - static CameraUpdate zoomOut() { - return CameraUpdate._(['zoomOut']); - } - - /// 创建一个指定缩放级别[zoom]的CameraUpdate对象 - /// - /// 主要用于设置地图缩放级别 - static CameraUpdate zoomTo(double zoom) { - return CameraUpdate._(['zoomTo', zoom]); - } - - final dynamic _json; - - dynamic toJson() => _json; -} diff --git a/local_plugins/amap_service/lib/types/marker.dart b/local_plugins/amap_service/lib/types/marker.dart deleted file mode 100644 index 2c5190452..000000000 --- a/local_plugins/amap_service/lib/types/marker.dart +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' show Offset; -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'base_overlay.dart'; -import 'bitmap.dart'; - -/// Marker拖动回调 -typedef void MarkerDragEndCallback(String id, LatLng endPosition); - -///Marker的气泡 -/// -///Android和iOS的实现机制有差异,仅在接口层面拉齐,效果一致 -class InfoWindow { - /// 为 [Marker] 产生一个不可修改的文本气泡. - const InfoWindow({this.title, this.snippet}); - - /// 无文本的气泡 - static const InfoWindow noText = InfoWindow(); - - /// 气泡的title - final String? title; - - /// 气泡的详细信息 - final String? snippet; - - /// 气泡copy方法 - /// - InfoWindow copyWith({String? titleParam, String? snippetParam}) { - return InfoWindow( - title: titleParam ?? title, - snippet: snippetParam ?? snippet, - ); - } - - Map _toMap() { - final Map json = {}; - - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('title', title); - addIfPresent('snippet', snippet); - return json; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is! InfoWindow) { - return false; - } - final InfoWindow typedOther = other; - return title == typedOther.title && snippet == typedOther.snippet; - } - - @override - int get hashCode => Object.hash(title, snippet); - - @override - String toString() { - return 'InfoWindow{title: $title, snippet: $snippet}'; - } -} - -/// 点覆盖物的类 -class Marker extends BaseOverlay { - Marker({ - required this.position, - double alpha = 1.0, - Offset anchor = const Offset(0.5, 1.0), - this.clickable = true, - this.draggable = false, - this.icon = BitmapDescriptor.defaultMarker, - this.infoWindowEnable = true, - this.infoWindow = InfoWindow.noText, - this.rotation = 0.0, - this.visible = true, - this.zIndex = 0.0, - this.onTap, - this.onDragEnd, - }) : this.alpha = - // ignore: unnecessary_null_comparison - (alpha != null ? (alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)) : alpha), - // ignore: unnecessary_null_comparison - this.anchor = - (anchor == null - ? Offset(0.5, 1.0) - : ((anchor.dx < 0 || - anchor.dx > 1 || - anchor.dy < 0 || - anchor.dy > 1) - ? Offset(0.5, 1.0) - : anchor)), - super(); - - /// 透明度 - final double alpha; - - /// 覆盖物视图相对地图上的经纬度位置的锚点 - final Offset anchor; - - /// 是否可点击,默认为true - final bool clickable; - - /// 是否可拖拽,默认为false - final bool draggable; - - /// 覆盖物的图标 - final BitmapDescriptor icon; - - /// 是否显示气泡,如果为true,则点击[Marker]后,会显示该气泡[InfoWindow] - /// 如果为false,则始终不会显示该气泡 - final bool infoWindowEnable; - - /// 覆盖物上的气泡,当被点击时,如果infoWindowEnable为true,则会显示出来 - final InfoWindow infoWindow; - - /// 位置,不能为空 - final LatLng position; - - /// 旋转角度,以锚点为中心,顺时针旋转(单位:度数) - /// - /// 注意:iOS端目前仅支持绕marker中心点旋转 - final double rotation; - - /// 是否可见 - final bool visible; - - /// z轴的值,用于调整该覆盖物的相对绘制层级关系 - /// 值越小,图层越靠下,iOS该值不支持动态修改,仅能在初始化时指定 - final double zIndex; - - /// 回调的参数是对应的id - final ArgumentCallback? onTap; - - /// Marker被拖拽结束的回调 - final MarkerDragEndCallback? onDragEnd; - - /// copy的真正复制的参数,主要用于需要修改某个属性参数时使用 - Marker copyWith({ - double? alphaParam, - Offset? anchorParam, - bool? clickableParam, - bool? draggableParam, - BitmapDescriptor? iconParam, - bool? infoWindowEnableParam, - InfoWindow? infoWindowParam, - LatLng? positionParam, - double? rotationParam, - bool? visibleParam, - ArgumentCallback? onTapParam, - MarkerDragEndCallback? onDragEndParam, - }) { - Marker copyMark = Marker( - alpha: alphaParam ?? alpha, - anchor: anchorParam ?? anchor, - clickable: clickableParam ?? clickable, - draggable: draggableParam ?? draggable, - icon: iconParam ?? icon, - infoWindowEnable: infoWindowEnableParam ?? infoWindowEnable, - infoWindow: infoWindowParam ?? infoWindow, - position: positionParam ?? position, - rotation: rotationParam ?? rotation, - visible: visibleParam ?? visible, - zIndex: zIndex, - onTap: onTapParam ?? onTap, - onDragEnd: onDragEndParam ?? onDragEnd, - ); - copyMark.setIdForCopy(id); - return copyMark; - } - - Marker clone() => copyWith(); - - @override - Map toMap() { - final Map json = {}; - - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('id', id); - addIfPresent('alpha', alpha); - addIfPresent('anchor', _offsetToJson(anchor)); - addIfPresent('clickable', clickable); - addIfPresent('draggable', draggable); - addIfPresent('icon', icon.toMap()); - addIfPresent('infoWindowEnable', infoWindowEnable); - addIfPresent('infoWindow', infoWindow._toMap()); - addIfPresent('position', position.toJson()); - addIfPresent('rotation', rotation); - addIfPresent('visible', visible); - addIfPresent('zIndex', zIndex); - return json; - } - - dynamic _offsetToJson(Offset offset) { - return [offset.dx, offset.dy]; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is! Marker) return false; - final Marker typedOther = other; - return id == typedOther.id && - alpha == typedOther.alpha && - anchor == typedOther.anchor && - clickable == typedOther.clickable && - draggable == typedOther.draggable && - icon == typedOther.icon && - infoWindowEnable == typedOther.infoWindowEnable && - infoWindow == typedOther.infoWindow && - position == typedOther.position && - rotation == typedOther.rotation && - visible == typedOther.visible && - zIndex == typedOther.zIndex; - } - - @override - int get hashCode => super.hashCode; - - @override - String toString() { - return 'Marker{id: $id, alpha: $alpha, anchor: $anchor, ' - 'clickable: $clickable, draggable: $draggable,' - 'icon: $icon, infoWindowEnable: $infoWindowEnable, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' - 'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; - } -} - -Map keyByMarkerId(Iterable markers) { - return Map.fromEntries( - markers.map( - (Marker marker) => MapEntry(marker.id, marker.clone()), - ), - ); -} diff --git a/local_plugins/amap_service/lib/types/marker_updates.dart b/local_plugins/amap_service/lib/types/marker_updates.dart deleted file mode 100644 index e69ad4b1c..000000000 --- a/local_plugins/amap_service/lib/types/marker_updates.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:ui' show hashValues; - -import 'package:flutter/foundation.dart' show setEquals; - -import 'types.dart'; -import 'marker.dart'; - -/// 用以描述Marker的更新项 -class MarkerUpdates { - /// 根据之前的marker列表[previous]和当前的marker列表[current]创建[MakerUpdates]. - MarkerUpdates.from(Set previous, Set current) { - // ignore: unnecessary_null_comparison - if (previous == null) { - previous = Set.identity(); - } - - // ignore: unnecessary_null_comparison - if (current == null) { - current = Set.identity(); - } - - final Map previousMarkers = keyByMarkerId(previous); - final Map currentMarkers = keyByMarkerId(current); - - final Set prevMarkerIds = previousMarkers.keys.toSet(); - final Set currentMarkerIds = currentMarkers.keys.toSet(); - - Marker idToCurrentMarker(String id) { - return currentMarkers[id]!; - } - - final Set _markerIdsToRemove = prevMarkerIds.difference( - currentMarkerIds, - ); - - final Set _markersToAdd = - currentMarkerIds - .difference(prevMarkerIds) - .map(idToCurrentMarker) - .toSet(); - - bool hasChanged(Marker current) { - final Marker? previous = previousMarkers[current.id]; - return current != previous; - } - - final Set _markersToChange = - currentMarkerIds - .intersection(prevMarkerIds) - .map(idToCurrentMarker) - .where(hasChanged) - .toSet(); - - markersToAdd = _markersToAdd; - markerIdsToRemove = _markerIdsToRemove; - markersToChange = _markersToChange; - } - - /// 想要添加的marker集合. - Set? markersToAdd; - - /// 想要删除的marker的id集合 - Set? markerIdsToRemove; - - /// 想要更新的marker集合. - Set? markersToChange; - - Map toMap() { - final Map updateMap = {}; - - void addIfNonNull(String fieldName, dynamic value) { - if (value != null) { - updateMap[fieldName] = value; - } - } - - addIfNonNull('markersToAdd', serializeOverlaySet(markersToAdd!)); - addIfNonNull('markersToChange', serializeOverlaySet(markersToChange!)); - addIfNonNull('markerIdsToRemove', markerIdsToRemove?.toList()); - - return updateMap; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is! MarkerUpdates) return false; - final MarkerUpdates typedOther = other; - return setEquals(markersToAdd, typedOther.markersToAdd) && - setEquals(markerIdsToRemove, typedOther.markerIdsToRemove) && - setEquals(markersToChange, typedOther.markersToChange); - } - - @override - int get hashCode => - Object.hash(markersToAdd, markerIdsToRemove, markersToChange); - - @override - String toString() { - return '_MarkerUpdates{markersToAdd: $markersToAdd, ' - 'markerIdsToRemove: $markerIdsToRemove, ' - 'markersToChange: $markersToChange}'; - } -} diff --git a/local_plugins/amap_service/lib/types/polygon.dart b/local_plugins/amap_service/lib/types/polygon.dart deleted file mode 100644 index a780cfe98..000000000 --- a/local_plugins/amap_service/lib/types/polygon.dart +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart' show listEquals; -import 'package:flutter/material.dart' show Color; -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'base_overlay.dart'; -import 'polyline.dart'; - -/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 -class Polygon extends BaseOverlay { - /// 默认构造函数 - Polygon( - {required this.points, - double strokeWidth = 10, - this.strokeColor = const Color(0xCC00BFFF), - this.fillColor = const Color(0xC487CEFA), - this.visible = true, - this.joinType = JoinType.bevel}) - : assert(points.length > 0), - this.strokeWidth = (strokeWidth <= 0 ? 10 : strokeWidth), - super(); - - /// 覆盖物的坐标点数组,不能为空 - final List points; - - /// 边框宽度,单位为逻辑像素,同Android中的dp,iOS中的point - final double strokeWidth; - - /// 边框颜色,默认值为(0xCCC4E0F0) - final Color strokeColor; - - /// 填充颜色,默认值为(0xC4E0F0CC) - final Color fillColor; - - /// 是否可见 - final bool visible; - - /// 连接点类型,该参数不支持copy时修改,仅能在初始化时设置一次 - final JoinType joinType; - - /// 实际copy函数 - Polygon copyWith({ - List? pointsParam, - double? strokeWidthParam, - Color? strokeColorParam, - Color? fillColorParam, - bool? visibleParam, - }) { - Polygon copyPolyline = Polygon( - points: pointsParam ?? points, - strokeWidth: strokeWidthParam ?? strokeWidth, - strokeColor: strokeColorParam ?? strokeColor, - fillColor: fillColorParam ?? fillColor, - visible: visibleParam ?? visible, - joinType: joinType, - ); - copyPolyline.setIdForCopy(id); - return copyPolyline; - } - - Polygon clone() => copyWith(); - - /// 转换成可以序列化的map - @override - Map toMap() { - final Map json = {}; - - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('id', id); - json['points'] = _pointsToJson(); - addIfPresent('strokeWidth', strokeWidth); - addIfPresent('strokeColor', strokeColor.value); - addIfPresent('fillColor', fillColor.value); - addIfPresent('visible', visible); - addIfPresent('joinType', joinType.index); - return json; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is !Polygon) return false; - final Polygon typedOther = other; - return id == typedOther.id && - listEquals(points, typedOther.points) && - strokeWidth == typedOther.strokeWidth && - strokeColor == typedOther.strokeColor && - fillColor == typedOther.fillColor && - visible == typedOther.visible && - joinType == typedOther.joinType; - } - - @override - int get hashCode => super.hashCode; - - dynamic _pointsToJson() { - final List result = []; - for (final LatLng point in points) { - result.add(point.toJson()); - } - return result; - } -} - -Map keyByPolygonId(Iterable polylines) { - // ignore: unnecessary_null_comparison - if (polylines == null) { - return {}; - } - return Map.fromEntries(polylines.map((Polygon polyline) => - MapEntry(polyline.id, polyline.clone()))); -} diff --git a/local_plugins/amap_service/lib/types/polygon_updates.dart b/local_plugins/amap_service/lib/types/polygon_updates.dart deleted file mode 100644 index 0584b0d9a..000000000 --- a/local_plugins/amap_service/lib/types/polygon_updates.dart +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' show hashValues; - -import 'package:flutter/foundation.dart' show setEquals; -import 'types.dart'; - -/// 该类主要用以描述[Polygon]的增删改等更新操作 -class PolygonUpdates { - /// 通过Polygon的前后更新集合构造一个PolygonUpdates - PolygonUpdates.from(Set previous, Set current) { - // ignore: unnecessary_null_comparison - if (previous == null) { - previous = Set.identity(); - } - - // ignore: unnecessary_null_comparison - if (current == null) { - current = Set.identity(); - } - - final Map previousPolygons = keyByPolygonId(previous); - final Map currentPolygons = keyByPolygonId(current); - - final Set prevPolygonIds = previousPolygons.keys.toSet(); - final Set currentPolygonIds = currentPolygons.keys.toSet(); - - Polygon idToCurrentPolygon(String id) { - return currentPolygons[id]!; - } - - final Set _polygonIdsToRemove = prevPolygonIds.difference( - currentPolygonIds, - ); - - final Set _polygonsToAdd = - currentPolygonIds - .difference(prevPolygonIds) - .map(idToCurrentPolygon) - .toSet(); - - bool hasChanged(Polygon current) { - final Polygon previous = previousPolygons[current.id]!; - return current != previous; - } - - final Set _polygonsToChange = - currentPolygonIds - .intersection(prevPolygonIds) - .map(idToCurrentPolygon) - .where(hasChanged) - .toSet(); - - polygonsToAdd = _polygonsToAdd; - polygonIdsToRemove = _polygonIdsToRemove; - polygonsToChange = _polygonsToChange; - } - - /// 想要添加的polygon对象集合. - Set? polygonsToAdd; - - /// 想要删除的polygon的id集合 - Set? polygonIdsToRemove; - - /// 想要更新的polygon对象集合 - Set? polygonsToChange; - - /// 转换成可以序列化的map - Map toMap() { - final Map updateMap = {}; - - void addIfNonNull(String fieldName, dynamic value) { - if (value != null) { - updateMap[fieldName] = value; - } - } - - addIfNonNull('polygonsToAdd', serializeOverlaySet(polygonsToAdd!)); - addIfNonNull('polygonsToChange', serializeOverlaySet(polygonsToChange!)); - addIfNonNull('polygonIdsToRemove', polygonIdsToRemove?.toList()); - - return updateMap; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is! PolygonUpdates) return false; - final PolygonUpdates typedOther = other; - return setEquals(polygonsToAdd, typedOther.polygonsToAdd) && - setEquals(polygonIdsToRemove, typedOther.polygonIdsToRemove) && - setEquals(polygonsToChange, typedOther.polygonsToChange); - } - - @override - int get hashCode => - Object.hash(polygonsToAdd, polygonIdsToRemove, polygonsToChange); - - @override - String toString() { - return '_PolygonUpdates{polygonsToAdd: $polygonsToAdd, ' - 'polygonIdsToRemove: $polygonIdsToRemove, ' - 'polygonsToChange: $polygonsToChange}'; - } -} diff --git a/local_plugins/amap_service/lib/types/polyline.dart b/local_plugins/amap_service/lib/types/polyline.dart deleted file mode 100644 index 58506ec1a..000000000 --- a/local_plugins/amap_service/lib/types/polyline.dart +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:amap_flutter_map/src/types/bitmap.dart'; -import 'package:flutter/foundation.dart' show listEquals; -import 'package:flutter/material.dart' show Color; -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'base_overlay.dart'; - -/// 虚线类型 -enum DashLineType { - /// 不画虚线 - none, - - /// 方块样式 - square, - - /// 圆点样式 - circle, -} - -/// 线头类型 -enum CapType { - /// 普通头 - butt, - - /// 扩展头 - square, - - /// 箭头 - arrow, - - /// 圆形头 - round, -} - -/// 连接点类型 -enum JoinType { - /// 斜面连接点 - bevel, - - /// 斜接连接点 - miter, - - /// 圆角连接点 - round, -} - -/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 -class Polyline extends BaseOverlay { - /// 默认构造函数 - Polyline({ - required this.points, - double width = 10, - this.visible = true, - this.geodesic = false, - double alpha = 1.0, - this.dashLineType = DashLineType.none, - this.capType = CapType.butt, - this.joinType = JoinType.bevel, - this.customTexture, - this.onTap, - this.color = const Color(0xCCC4E0F0), - }) : assert(points.length > 0), - this.width = (width <= 0 ? 10 : width), - this.alpha =(alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)), - super(); - - /// 覆盖物的坐标点数组,points不能为空 - final List points; - - /// 线宽,单位为逻辑像素,同Android中的dp,iOS中的point - final double width; - - /// 是否可见 - final bool visible; - - /// 透明度 - final double alpha; - - /// 覆盖物颜色,默认值为(0xCCC4E0F0). - final Color color; - - /// 自定义纹理图片,注意: 如果设置了自定义纹理图片,则color的设置将无效; - final BitmapDescriptor? customTexture; - - /// 是否为大地曲线 - final bool geodesic; - - /// 虚线类型 - final DashLineType dashLineType; - - /// 连接点类型 - final JoinType joinType; - - /// 线头类型 - final CapType capType; - - /// 点击回调(回调参数为id) - final ArgumentCallback? onTap; - - /// 实际copy函数 - Polyline copyWith({ - List? pointsParam, - double? widthParam, - int? zIndexParam, - bool? visibleParam, - double? alphaParam, - DashLineType? dashLineTypeParam, - CapType? capTypeParam, - JoinType? joinTypeParam, - BitmapDescriptor? customTextureParam, - ArgumentCallback? onTapParam, - Color? colorParam, - }) { - Polyline copyPolyline = Polyline( - points: pointsParam ?? points, - width: widthParam ?? width, - visible: visibleParam ?? visible, - geodesic: geodesic, - alpha: alphaParam ?? alpha, - dashLineType: dashLineTypeParam ?? dashLineType, - capType: capTypeParam ?? capType, - joinType: joinTypeParam ?? joinType, - customTexture: customTextureParam ?? customTexture, - onTap: onTapParam ?? onTap, - color: colorParam ?? color, - ); - copyPolyline.setIdForCopy(id); - return copyPolyline; - } - - Polyline clone() => copyWith(); - - /// 将对象转换为可序列化的map. - @override - Map toMap() { - final Map json = {}; - - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('id', id); - json['points'] = _pointsToJson(); - addIfPresent('width', width); - addIfPresent('visible', visible); - addIfPresent('geodesic', geodesic); - addIfPresent('alpha', alpha); - addIfPresent('dashLineType', dashLineType.index); - addIfPresent('capType', capType.index); - addIfPresent('joinType', joinType.index); - addIfPresent('customTexture', customTexture?.toMap()); - addIfPresent('color', color.value); - return json; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is !Polyline) return false; - final Polyline typedOther = other; - return id == typedOther.id && - listEquals(points, typedOther.points) && - width == typedOther.width && - visible == typedOther.visible && - geodesic == typedOther.geodesic && - alpha == typedOther.alpha && - dashLineType == typedOther.dashLineType && - capType == typedOther.capType && - joinType == typedOther.joinType && - color == typedOther.color; - } - - @override - int get hashCode => super.hashCode; - - dynamic _pointsToJson() { - final List result = []; - for (final LatLng point in points) { - result.add(point.toJson()); - } - return result; - } -} - -Map keyByPolylineId(Iterable polylines) { - // ignore: unnecessary_null_comparison - if (polylines == null) { - return {}; - } - return Map.fromEntries(polylines.map((Polyline polyline) => - MapEntry(polyline.id, polyline.clone()))); -} diff --git a/local_plugins/amap_service/lib/types/polyline_updates.dart b/local_plugins/amap_service/lib/types/polyline_updates.dart deleted file mode 100644 index 29d485d15..000000000 --- a/local_plugins/amap_service/lib/types/polyline_updates.dart +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart' show setEquals; - -import 'polyline.dart'; -import 'types.dart'; - -/// 该类主要用以描述[Polyline]的增删改等更新操作 -class PolylineUpdates { - /// 通过polyline的前后更新集合构造一个polylineUpdates - PolylineUpdates.from(Set previous, Set current) { - // ignore: unnecessary_null_comparison - if (previous == null) { - previous = Set.identity(); - } - - // ignore: unnecessary_null_comparison - if (current == null) { - current = Set.identity(); - } - - final Map previousPolylines = keyByPolylineId(previous); - final Map currentPolylines = keyByPolylineId(current); - - final Set prevPolylineIds = previousPolylines.keys.toSet(); - final Set currentPolylineIds = currentPolylines.keys.toSet(); - - Polyline idToCurrentPolyline(String id) { - return currentPolylines[id]!; - } - - final Set _polylineIdsToRemove = prevPolylineIds.difference( - currentPolylineIds, - ); - - final Set _polylinesToAdd = - currentPolylineIds - .difference(prevPolylineIds) - .map(idToCurrentPolyline) - .toSet(); - - bool hasChanged(Polyline current) { - final Polyline previous = previousPolylines[current.id]!; - return current != previous; - } - - final Set _polylinesToChange = - currentPolylineIds - .intersection(prevPolylineIds) - .map(idToCurrentPolyline) - .where(hasChanged) - .toSet(); - - polylinesToAdd = _polylinesToAdd; - polylineIdsToRemove = _polylineIdsToRemove; - polylinesToChange = _polylinesToChange; - } - - /// 用于添加polyline的集合 - Set? polylinesToAdd; - - /// 需要删除的plyline的id集合 - Set? polylineIdsToRemove; - - /// 用于更新polyline的集合 - Set? polylinesToChange; - - /// 将对象装换为可序列化的对象 - Map toMap() { - final Map updateMap = {}; - - void addIfNonNull(String fieldName, dynamic value) { - if (value != null) { - updateMap[fieldName] = value; - } - } - - addIfNonNull('polylinesToAdd', serializeOverlaySet(polylinesToAdd!)); - addIfNonNull('polylinesToChange', serializeOverlaySet(polylinesToChange!)); - addIfNonNull('polylineIdsToRemove', polylineIdsToRemove?.toList()); - - return updateMap; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; - if (other is! PolylineUpdates) return false; - final PolylineUpdates typedOther = other; - return setEquals(polylinesToAdd, typedOther.polylinesToAdd) && - setEquals(polylineIdsToRemove, typedOther.polylineIdsToRemove) && - setEquals(polylinesToChange, typedOther.polylinesToChange); - } - - @override - int get hashCode => - Object.hash(polylinesToAdd, polylineIdsToRemove, polylinesToChange); - - @override - String toString() { - return '_PolylineUpdates{polylinesToAdd: $polylinesToAdd, ' - 'polylineIdsToRemove: $polylineIdsToRemove, ' - 'polylinesToChange: $polylinesToChange}'; - } -} diff --git a/local_plugins/amap_service/lib/types/types.dart b/local_plugins/amap_service/lib/types/types.dart deleted file mode 100644 index e62300549..000000000 --- a/local_plugins/amap_service/lib/types/types.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'camera.dart'; -export 'ui.dart'; -export 'base_overlay.dart'; -export 'marker.dart'; -export 'marker_updates.dart'; -export 'polyline.dart'; -export 'polyline_updates.dart'; -export 'polygon.dart'; -export 'polygon_updates.dart'; -export 'camera.dart'; -export 'bitmap.dart'; diff --git a/local_plugins/amap_service/lib/types/ui.dart b/local_plugins/amap_service/lib/types/ui.dart deleted file mode 100644 index e33409197..000000000 --- a/local_plugins/amap_service/lib/types/ui.dart +++ /dev/null @@ -1,257 +0,0 @@ -import 'dart:typed_data'; -import 'dart:ui' show Color; -import 'package:amap_flutter_base/amap_flutter_base.dart'; -import 'package:amap_flutter_map/amap_flutter_map.dart'; - -/// 地图类型 -enum MapType { - /// 普通地图 - normal, - - /// 卫星地图 - satellite, - - /// 夜间视图 - night, - - /// 导航视图 - navi, - - /// 公交视图 - bus, -} - -// 设置摄像机的边界. -class CameraTargetBounds { - /// 使用指定的边界框或空值创建摄影机目标边界 - /// - /// 设置为null时代表不指定边界 - const CameraTargetBounds(this.bounds); - - /// 摄像机的边界. - /// - /// null代表不指定边界 - final LatLngBounds? bounds; - - /// 取消指定边界 - static const CameraTargetBounds unbounded = CameraTargetBounds(null); - - /// 转换成json对象 - dynamic toJson() => [bounds?.toJson()]; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; - final CameraTargetBounds typedOther = other; - return bounds == typedOther.bounds; - } - - @override - int get hashCode => bounds.hashCode; - - @override - String toString() { - return 'CameraTargetBounds(bounds: $bounds)'; - } -} - -/// 地图最大最小缩放级别的封装对象 -class MinMaxZoomPreference { - /// 为地图创建一个不可变的最大最小缩放范围 - /// - /// 缩放级别范围为[3, 20],超出范围取边界值 - /// - const MinMaxZoomPreference(double minZoom, double maxZoom) - : this.minZoom = - ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) - ? maxZoom - : minZoom), - this.maxZoom = - ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) - ? minZoom - : maxZoom); - - /// 最小zoomLevel - final double? minZoom; - - /// 最大zoomLevel - final double? maxZoom; - - /// 高德地图默认zoomLevel的范围. - static const MinMaxZoomPreference defaultPreference = MinMaxZoomPreference( - 3, - 20, - ); - - /// JSON序列化. - dynamic toJson() => [minZoom, maxZoom]; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; - final MinMaxZoomPreference typedOther = other; - return minZoom == typedOther.minZoom && maxZoom == typedOther.maxZoom; - } - - @override - int get hashCode => Object.hash(minZoom, maxZoom); - - @override - String toString() { - return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; - } -} - -///定位小蓝点配置项 -class MyLocationStyleOptions { - ///是否显示定位小蓝点 - bool enabled; - - ///精度圈填充色 - Color? circleFillColor; - - ///精度圈边框色 - Color? circleStrokeColor; - - ///精度圈边框宽度 - double? circleStrokeWidth; - - ///小蓝点图标 - BitmapDescriptor? icon; - - MyLocationStyleOptions( - this.enabled, { - this.circleFillColor, - this.circleStrokeColor, - this.circleStrokeWidth, - this.icon, - }); - - MyLocationStyleOptions clone() { - return MyLocationStyleOptions( - enabled, - circleFillColor: circleFillColor, - circleStrokeColor: circleStrokeColor, - circleStrokeWidth: circleStrokeWidth, - icon: icon, - ); - } - - static MyLocationStyleOptions? fromMap(dynamic json) { - if (null == json) { - return null; - } - return MyLocationStyleOptions( - json['enabled'] ?? false, - circleFillColor: json['circleFillColor'] ?? null, - circleStrokeColor: json['circleStrokeColor'] ?? null, - circleStrokeWidth: json['circleStrokeWidth'] ?? null, - icon: json['icon'] ?? null, - ); - } - - Map toMap() { - final Map json = {}; - - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('enabled', enabled); - addIfPresent('circleFillColor', circleFillColor?.value); - addIfPresent('circleStrokeColor', circleStrokeColor?.value); - addIfPresent('circleStrokeWidth', circleStrokeWidth); - addIfPresent('icon', icon?.toMap()); - return json; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; - if (other is! MyLocationStyleOptions) return false; - final MyLocationStyleOptions typedOther = other; - return enabled == typedOther.enabled && - circleFillColor == typedOther.circleFillColor && - circleStrokeColor == typedOther.circleStrokeColor && - icon == typedOther.icon; - } - - @override - String toString() { - return 'MyLocationOptionsStyle{' - 'enabled: $enabled,' - 'circleFillColor: $circleFillColor,' - 'circleStrokeColor: $circleStrokeColor,' - 'icon: $icon, }'; - } - - @override - int get hashCode => - Object.hash(enabled, circleFillColor, circleStrokeColor, icon); -} - -///地图自定义样式 -class CustomStyleOptions { - ///开关项,是否开启自定义地图 - bool enabled; - - ///自定义样式的二进制数据,对应下载的自定义地图文件中的style.data中的二进制数据 - Uint8List? styleData; - - ///自定义扩展样式的二进制数据,对应下载的自定义地图文件中的style_extra.data中的二进制数据 - Uint8List? styleExtraData; - - CustomStyleOptions(this.enabled, {this.styleData, this.styleExtraData}); - - static CustomStyleOptions? fromMap(dynamic json) { - if (json == null) { - return null; - } - return CustomStyleOptions( - json['enabled'] ?? false, - styleData: json['styleData'] ?? null, - styleExtraData: json['styleExtraData'] ?? null, - ); - } - - dynamic toMap() { - final Map json = {}; - void addIfPresent(String fieldName, dynamic value) { - if (value != null) { - json[fieldName] = value; - } - } - - addIfPresent('enabled', enabled); - addIfPresent('styleData', styleData); - addIfPresent('styleExtraData', styleExtraData); - return json; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (runtimeType != other.runtimeType) return false; - if (other is! CustomStyleOptions) return false; - final CustomStyleOptions typedOther = other; - return enabled == typedOther.enabled && - styleData == typedOther.styleData && - styleExtraData == typedOther.styleExtraData; - } - - @override - int get hashCode => Object.hash(enabled, styleData, styleExtraData); - - CustomStyleOptions clone() { - return CustomStyleOptions( - enabled, - styleData: styleData, - styleExtraData: styleExtraData, - ); - } -} diff --git a/local_plugins/amap_service/pubspec.yaml b/local_plugins/amap_service/pubspec.yaml index 66d9bcad4..5cb50340b 100644 --- a/local_plugins/amap_service/pubspec.yaml +++ b/local_plugins/amap_service/pubspec.yaml @@ -16,8 +16,6 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 - amap_flutter_base: ^3.0.0 - amap_flutter_map: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock index e43ebb023b9841b545f849e66b8205d102fc46e3..8ed41b458b42dc9a4378acb682ea708b1bb56cea 100644 GIT binary patch literal 17 TcmZQ}Dp$CADo{Lz0RjvFDKi6b literal 17 TcmZQ}Dp$CADo{Lz0Rr>^DKZ0Z 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 From e4420f00293e95122decbed87312d600ac3690d8 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 21 May 2025 19:58:53 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/translations/language/zh_cn.dart | 6 + lib/modules/agent/views/message_bubble.dart | 132 ++++++++++-------- .../amap_service/NavigationActivity.kt | 3 +- 3 files changed, 80 insertions(+), 61 deletions(-) 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/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index e2cae94cd..0d945fc65 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -284,6 +284,7 @@ class MessageBubble extends GetView { // 旅游 return _buildTravelContent(context, travellist); } else if (meta!.keys.contains('card_navigation')) { + 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']; @@ -291,7 +292,8 @@ class MessageBubble extends GetView { rawNavigation is Map ? rawNavigation : null; print("导航实时数据 ${rawNavigation}"); // 导航 - return navigationCard(context, modes: list, navigation: navigation); + return navigationCard(context, + destination: destination, modes: list, navigation: navigation); } // if (message.contains('播放列表') || message.contains('音乐')) { @@ -1173,8 +1175,37 @@ class MessageBubble extends GetView { return Icons.help_outline; } + 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 destination, required List> modes, Map? navigation, double height = 300, @@ -1224,16 +1255,22 @@ class MessageBubble extends GetView { children: List.generate(modes.length, (index) { final isSelected = index == currentIndex; return GestureDetector( - onTap: () { - setInnerState(() => currentIndex = index); - }, + onTap: modes[index]['enable'] as bool + ? () { + setInnerState(() => currentIndex = index); + } + : null, child: SizedBox( width: 50, child: Text( - modes[index]["ntype"] as String, + (modes[index]["ntype"] as String).tr, textAlign: TextAlign.center, style: TextStyle( - color: isSelected ? Colors.blue : Colors.black87, + color: modes[index]['enable'] + ? isSelected + ? Colors.blue + : Colors.black87 + : Colors.grey, fontSize: 12, fontWeight: FontWeight.w500, ), @@ -1250,64 +1287,39 @@ class MessageBubble extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('距离:${modes[currentIndex]["distance"]}', - style: - const TextStyle(fontSize: 12, color: Colors.blue)), - IconButton( - icon: const Icon(Icons.turn_left, - color: Colors.blue, size: 20), - onPressed: () { - controller.openNavigation(); - }, + 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(); + }, + ), + ) ], ), ), - - // 导航实时数据显示 - 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/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 index 5ba631c52..0c2f8e5f0 100644 --- 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 @@ -212,12 +212,13 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi } override fun onNaviCancel() { + finish() // TODO("Not yet implemented") } override fun onNaviBackClick(): Boolean { // TODO("Not yet implemented") - return true + return false } override fun onNaviMapMode(p0: Int) { From 0c2c273453f80229ad5926cc1c8605e5aea20fd6 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 16:21:45 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/navigation_manager.dart | 8 ++- .../agent/controllers/agent_controller.dart | 16 +++++- lib/modules/agent/views/message_bubble.dart | 12 +++- .../agent_service/AgentService.kt | 2 +- .../android/src/main/AndroidManifest.xml | 3 + .../amap_service/AmapServicePlugin.kt | 9 ++- .../amap_service/NavigationActivity.kt | 52 +++++++++++++++--- .../amap_service/NavigationService.kt | 48 +++++++++------- .../amap_service/NavigationServiceHelper.kt | 29 +++++----- .../amap_service/lib/amap_service.dart | 8 ++- .../open_ai_service/SystemFunctionHandler.kt | 55 ++++++++++++++++++- 11 files changed, 187 insertions(+), 55 deletions(-) diff --git a/lib/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index 924ef0877..5604838c7 100644 --- a/lib/data/services/navigation_manager.dart +++ b/lib/data/services/navigation_manager.dart @@ -31,7 +31,11 @@ class NavigationManager extends GetxService { // await _amapervice.navigation(org, dis); } //导航 - Future open() async { - await _amapervice.open(); + 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 6e81b5ce7..12c459295 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -397,7 +397,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'); } @@ -422,6 +422,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(seconds: 2), () { + Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); + // 2秒后执行的代码 + openNavigation(start, end, ""); + }); + } } } break; @@ -849,7 +859,7 @@ class AgentController extends GetxController { } //去导航 - void openNavigation() { - _navigationManager.open(); + 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 0d945fc65..4e037f1e6 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -284,6 +284,8 @@ 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(); @@ -293,7 +295,11 @@ class MessageBubble extends GetView { print("导航实时数据 ${rawNavigation}"); // 导航 return navigationCard(context, - destination: destination, modes: list, navigation: navigation); + start: start, + end: end, + destination: destination, + modes: list, + navigation: navigation); } // if (message.contains('播放列表') || message.contains('音乐')) { @@ -1205,6 +1211,8 @@ class MessageBubble extends GetView { Widget navigationCard( BuildContext context, { + required String start, + required String end, required String destination, required List> modes, Map? navigation, @@ -1313,7 +1321,7 @@ class MessageBubble extends GetView { icon: const Icon(Icons.turn_left, color: Colors.white, size: 20), onPressed: () { - controller.openNavigation(); + controller.openNavigation(start, end, ""); }, ), ) 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 574237a89..13f8e3794 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 @@ -1186,7 +1186,7 @@ object AgentService : CoroutineScope { // 如果 playlist 中有有效的歌曲,开始播放 if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { Log.i(TAG, "启动导航服务 ${start} ${end}") - NavigationServiceHelper.startNavigation(context, startpos = start, endpos = end) + NavigationServiceHelper.startNavigation(context,"start", startpos = start, endpos = end) } else { Log.i(TAG, "启动导航服务失败") } diff --git a/local_plugins/amap_service/android/src/main/AndroidManifest.xml b/local_plugins/amap_service/android/src/main/AndroidManifest.xml index 019b115bd..624e09866 100644 --- a/local_plugins/amap_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/amap_service/android/src/main/AndroidManifest.xml @@ -41,6 +41,9 @@ 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 c3e5da152..e28e7a484 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 @@ -42,7 +42,7 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHan val endpos = call.argument("endpos") val ntype = call.argument("ntype") if (startpos != null && endpos != null && ntype != null) { - NavigationServiceHelper.startNavigation(context,startpos,endpos,ntype) + NavigationServiceHelper.startNavigation(context,"start",startpos,endpos,ntype) result.success(null) } else { result.error("INVALID_ARGUMENT", "Missing 'song' map", null) @@ -50,13 +50,16 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHan } "open" -> { try { - NavigationServiceHelper.openNavigationUI() + 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" -> { - NavigationServiceHelper.stopNavigation() + NavigationServiceHelper.startNavigation(context,"stop") result.success(null) } else -> { 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 index 0c2f8e5f0..384990bff 100644 --- 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 @@ -1,11 +1,14 @@ 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 @@ -18,24 +21,35 @@ 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) { - finish() // 服务未启动,退出 - return + navi = AMapNavi.getInstance(context).apply { + setUseInnerVoice(true) + setBroadcastMode(1) + } } navi?.addAMapNaviListener(this); @@ -53,9 +67,13 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi } override fun onDestroy() { - super.onDestroy() - naviView.onDestroy() + navi?.stopNavi() navi?.removeAMapNaviListener(this); + AMapNavi.destroy() + NavigationServiceHelper.startNavigation(context,"stop") + naviView.onDestroy() + super.onDestroy() + Log.i("NavigationActivity", "liwei------------------- onDestroy!") } override fun onInitNaviFailure() { @@ -63,6 +81,25 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi } 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") } @@ -167,6 +204,7 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi } override fun onCalculateRouteSuccess(p0: IntArray?) { + navi?.startNavi(1); // TODO("Not yet implemented") } @@ -212,7 +250,7 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi } override fun onNaviCancel() { - finish() + finishAndRemoveTask() // 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 7342a0c4d..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 @@ -60,18 +60,27 @@ class NavigationService: Service(),AMapNaviListener { } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - try { - startpos = intent.getStringExtra("startpos").toString() - endpos = intent.getStringExtra("endpos").toString() - naviType = intent.getStringExtra("naviType"); - Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") - if (startpos.isEmpty() || endpos.isEmpty()) { - Log.e(TAG, "Missing start or end position") + val command = intent.getStringExtra("command") ?: return START_NOT_STICKY + when (command) { + "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") + 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" -> { + stopNavigation() return START_NOT_STICKY } - initializeAMapNavi() - }catch (e: FileNotFoundException){ - Log.w("NavigationService", "Custom voice bin not found: ${e.message}") } return START_STICKY } @@ -148,6 +157,7 @@ class NavigationService: Service(),AMapNaviListener { if (sharedNavi != null) { sharedNavi?.stopNavi() sharedNavi?.removeAMapNaviListener(this) + sharedNavi = null } isRunning.set(false) stopSelf() @@ -271,14 +281,14 @@ class NavigationService: Service(),AMapNaviListener { 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) +// 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") @@ -340,7 +350,7 @@ class NavigationService: Service(),AMapNaviListener { override fun onCalculateRouteSuccess(p0: IntArray?) { Log.e(TAG, "onCalculateRouteSuccess! $p0") - sharedNavi?.startNavi(2); + sharedNavi?.startNavi(1); // TODO("Not yet implemented") } 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 index 34575ed62..500e45bf7 100644 --- 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 @@ -29,24 +29,27 @@ object NavigationServiceHelper { context = appContext } //启动导航 - fun startNavigation(context: Context, startpos: String? = null, endpos: String? = null, ntype: String? = null) { - Log.i(TAG, "liwei------------------- startNavigation: $startpos $endpos $ntype") + 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.startForegroundService(intent) - } - //停止导航 - fun stopNavigation() { - val intent = Intent(context, NavigationService::class.java) - context.stopService(intent) - } - //打开导航页面 - fun openNavigationUI() { - val intent = Intent(context, NavigationActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } diff --git a/local_plugins/amap_service/lib/amap_service.dart b/local_plugins/amap_service/lib/amap_service.dart index 29bc9d921..b90fd5434 100644 --- a/local_plugins/amap_service/lib/amap_service.dart +++ b/local_plugins/amap_service/lib/amap_service.dart @@ -98,8 +98,12 @@ class AmapService { await _methodChannel.invokeMethod('start'); } - Future open() async { - await _methodChannel.invokeMethod('open'); + Future open(String start, String end, String ntype) async { + await _methodChannel.invokeMethod('open', { + 'startpos': start, + "endpos": end, + "ntype": ntype, + }); } Future stop() 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}" + ) + } + } + /** * 操作结果数据类 */ From 6cd0a41fdc00826237dc421a5d743c5448797e2e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 16:21:55 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/controllers/agent_controller.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 12c459295..a40b55c7b 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -425,9 +425,9 @@ 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(seconds: 2), () { - Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); + // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); + Future.delayed(Duration(milliseconds: 1500), () { + // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); // 2秒后执行的代码 openNavigation(start, end, ""); }); From bb76e420dd62caed48fa7d15ef5ab57b39e98021 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 16:37:07 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/message_bubble.dart | 1 + lib/modules/agent/views/navigation_card.dart | 129 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 lib/modules/agent/views/navigation_card.dart diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index b8637b29a..14964195b 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:common_utils/common_utils.dart'; +import 'package:deep_voice/modules/agent/views/navigation_card.dart'; import 'weather_card.dart'; import 'music_card.dart'; import 'travel_card.dart'; diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart new file mode 100644 index 000000000..35c6fed55 --- /dev/null +++ b/lib/modules/agent/views/navigation_card.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/agent_controller.dart'; + +//导航卡片 +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, ""); + }, + ), + ) + ], + ), + ), + ], + ); + }, + ), + ); +} From ac46fb259bfc6de5e35ac94a1e15dca869353aa2 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 16:38:05 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=B7=9D=E7=A6=BB?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/navigation_card.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index 35c6fed55..886ae0cc9 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -2,6 +2,18 @@ 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)} 米'; + } +} + //导航卡片 Widget navigationCard( BuildContext context, { From 688003716fdd5a0b5a1317ede34b55db805d7e9e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 16:38:50 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/navigation_card.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index 886ae0cc9..0ad88dfe3 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -14,6 +14,22 @@ String formatDistance(String distanceStr) { } } +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, { From 59e907b063f6849cfced4675553b0c153db61331 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 22 May 2025 17:06:14 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E4=BF=9D=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yunqiinnovation/amap_service/NavigationActivity.kt | 7 +++++++ 1 file changed, 7 insertions(+) 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 index 384990bff..9009ec725 100644 --- 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 @@ -4,6 +4,7 @@ import android.content.Context import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity +import com.amap.api.location.AMapLocationClient import com.amap.api.navi.AMapNavi import com.amap.api.navi.AMapNaviListener import com.amap.api.navi.AMapNaviView @@ -35,6 +36,12 @@ class NavigationActivity : AppCompatActivity(), AMapNaviListener, AMapNaviViewLi super.onCreate(savedInstanceState) setContentView(R.layout.activity_navigation) context = this + + // 设置隐私政策展示状态 + AMapLocationClient.updatePrivacyShow(this, true, true) + // 设置用户同意隐私政策 + AMapLocationClient.updatePrivacyAgree(this, true) + // 初始化导航视图 naviView = findViewById(R.id.navi_view) naviView.onCreate(savedInstanceState)