diff --git a/lib/src/google_maps_map_view_controller.dart b/lib/src/google_maps_map_view_controller.dart index f7fe539..5dab80d 100644 --- a/lib/src/google_maps_map_view_controller.dart +++ b/lib/src/google_maps_map_view_controller.dart @@ -227,6 +227,15 @@ class GoogleMapViewController { /// Add markers to the map view. Future> addMarkers(List markerOptions) { + return GoogleMapsNavigationPlatform.instance.viewAPI.addMarkers( + viewId: _viewId, + markerOptions: + markerOptions.map((e) => (options: e, markerId: null)).toList()); + } + + // Add markers to the map view with custom id + Future> addMarkersWithCustomId( + List<({MarkerOptions options, String? markerId})> markerOptions) { return GoogleMapsNavigationPlatform.instance.viewAPI .addMarkers(viewId: _viewId, markerOptions: markerOptions); } @@ -265,6 +274,15 @@ class GoogleMapViewController { /// Add polygons to the map view. Future> addPolygons(List polygonOptions) { + return GoogleMapsNavigationPlatform.instance.viewAPI.addPolygons( + viewId: _viewId, + polygonOptions: + polygonOptions.map((e) => (options: e, polygonId: null)).toList()); + } + + // Add polygons to the map view with custom id + Future> addPolygonsWithCustomId( + List<({PolygonOptions options, String? polygonId})> polygonOptions) { return GoogleMapsNavigationPlatform.instance.viewAPI .addPolygons(viewId: _viewId, polygonOptions: polygonOptions); } @@ -303,6 +321,16 @@ class GoogleMapViewController { /// Add polylines to the map view. Future> addPolylines(List polylineOptions) { + return GoogleMapsNavigationPlatform.instance.viewAPI.addPolylines( + viewId: _viewId, + polylineOptions: (polylineOptions + .map((e) => (options: e, polylineId: null)) + .toList())); + } + + // Add polylines to the map view with custom id + Future> addPolylinesWithCustomId( + List<({PolylineOptions options, String? polylineId})> polylineOptions) { return GoogleMapsNavigationPlatform.instance.viewAPI .addPolylines(viewId: _viewId, polylineOptions: polylineOptions); } @@ -341,8 +369,17 @@ class GoogleMapViewController { /// Add circles to the map view. Future> addCircles(List options) { + return GoogleMapsNavigationPlatform.instance.viewAPI.addCircles( + viewId: _viewId, + circleOptions: + options.map((e) => (options: e, circleId: null)).toList()); + } + + // Add circles to the map view with custom id + Future> addCirclesWithCustomId( + List<({CircleOptions options, String? circleId})> circleOptions) { return GoogleMapsNavigationPlatform.instance.viewAPI - .addCircles(viewId: _viewId, options: options); + .addCircles(viewId: _viewId, circleOptions: circleOptions); } /// Update circles to the map view. diff --git a/lib/src/method_channel/map_view_api.dart b/lib/src/method_channel/map_view_api.dart index 735ddd1..7859138 100644 --- a/lib/src/method_channel/map_view_api.dart +++ b/lib/src/method_channel/map_view_api.dart @@ -31,33 +31,37 @@ class MapViewAPIImpl { StreamController<_ViewIdEventWrapper>.broadcast(); /// Keep track of marker count, used to generate marker ID's. + /// if markerId is provided, it will be used as the ID. int _markerCounter = 0; - String _createMarkerId() { - final String markerId = 'Marker_$_markerCounter'; + String _createMarkerIdIfNeeded({required String? markerId}) { + markerId ??= 'Marker_$_markerCounter'; _markerCounter += 1; return markerId; } /// Keep track of polygon count, used to generate polygon ID's. + /// if polygonId is provided, it will be used as the ID. int _polygonCounter = 0; - String _createPolygonId() { - final String polygonId = 'Polygon_$_polygonCounter'; + String _createPolygonIdIfNeeded({required String? polygonId}) { + polygonId ??= 'Polygon_$_polygonCounter'; _polygonCounter += 1; return polygonId; } /// Keep track of polyline count, used to generate polyline ID's. + /// if polylineId is provided, it will be used as the ID. int _polylineCounter = 0; - String _createPolylineId() { - final String polylineId = 'Polyline_$_polylineCounter'; + String _createPolylineIdIfNeeded({required String? polylineId}) { + polylineId ??= 'Polyline_$_polylineCounter'; _polylineCounter += 1; return polylineId; } /// Keep track of circle count, used to generate circle ID's. + /// if circleId is provided, it will be used as the ID. int _circleCounter = 0; - String _createCircleId() { - final String circleId = 'Circle_$_circleCounter'; + String _createCircleIdIfNeeded({required String? circleId}) { + circleId ??= 'Circle_$_circleCounter'; _circleCounter += 1; return circleId; } @@ -624,15 +628,22 @@ class MapViewAPIImpl { /// Add markers to map view. Future> addMarkers( - {required int viewId, required List markerOptions}) async { + {required int viewId, + required List<({MarkerOptions options, String? markerId})> + markerOptions}) async { // Convert options to pigeon format - final List options = - markerOptions.map((MarkerOptions opt) => opt.toDto()).toList(); - - // Create marker objects with new ID's + final List<({MarkerOptionsDto options, String? markerId})> options = + markerOptions + .map((e) => (options: e.options.toDto(), markerId: e.markerId)) + .toList(); + // Create marker objects + // Create new ID's for markers if not provided final List markersToAdd = options - .map((MarkerOptionsDto options) => - MarkerDto(markerId: _createMarkerId(), options: options)) + .map( + (e) => MarkerDto( + markerId: _createMarkerIdIfNeeded(markerId: e.markerId), + options: e.options), + ) .toList(); // Add markers to map @@ -709,15 +720,20 @@ class MapViewAPIImpl { /// Add polygons to map view. Future> addPolygons( {required int viewId, - required List polygonOptions}) async { + required List<({PolygonOptions options, String? polygonId})> + polygonOptions}) async { // Convert options to pigeon format - final List options = - polygonOptions.map((PolygonOptions opt) => opt.toDto()).toList(); + final List<({PolygonOptionsDto options, String? polygonId})> options = + polygonOptions + .map((e) => (options: e.options.toDto(), polygonId: e.polygonId)) + .toList(); - // Create polygon objects with new ID's + // Create polygon objects + // Create new ID's for polygons if not provided final List polygonsToAdd = options - .map((PolygonOptionsDto options) => - PolygonDto(polygonId: _createPolygonId(), options: options)) + .map((e) => PolygonDto( + polygonId: _createPolygonIdIfNeeded(polygonId: e.polygonId), + options: e.options)) .toList(); // Add polygons to map @@ -789,15 +805,20 @@ class MapViewAPIImpl { /// Add polylines to map view. Future> addPolylines( {required int viewId, - required List polylineOptions}) async { + required List<({PolylineOptions options, String? polylineId})> + polylineOptions}) async { // Convert options to pigeon format - final List options = - polylineOptions.map((PolylineOptions opt) => opt.toDto()).toList(); + final List<({PolylineOptionsDto options, String? polylineId})> options = + polylineOptions + .map((e) => (options: e.options.toDto(), polylineId: e.polylineId)) + .toList(); - // Create polyline objects with new ID's + // Create polyline objects + // Create new ID's for polylines if not provided final List polylinesToAdd = options - .map((PolylineOptionsDto options) => - PolylineDto(polylineId: _createPolylineId(), options: options)) + .map((e) => PolylineDto( + polylineId: _createPolylineIdIfNeeded(polylineId: e.polylineId), + options: e.options)) .toList(); // Add polylines to map @@ -870,15 +891,21 @@ class MapViewAPIImpl { /// Add circles to map view. Future> addCircles( - {required int viewId, required List options}) async { + {required int viewId, + required List<({CircleOptions options, String? circleId})> + circleOptions}) async { // Convert options to pigeon format - final List optionsDto = - options.map((CircleOptions opt) => opt.toDto()).toList(); - - // Create circle objects with new ID's - final List circlesToAdd = optionsDto - .map((CircleOptionsDto options) => - CircleDto(circleId: _createCircleId(), options: options)) + final List<({CircleOptionsDto options, String? circleId})> options = + circleOptions + .map((e) => (options: e.options.toDto(), circleId: e.circleId)) + .toList(); + + // Create circle objects + // Create new ID's for circles if not provided + final List circlesToAdd = options + .map((e) => CircleDto( + circleId: _createCircleIdIfNeeded(circleId: e.circleId), + options: e.options)) .toList(); // Add circles to map diff --git a/test/google_navigation_flutter_test.dart b/test/google_navigation_flutter_test.dart index 99a186b..4ef52c8 100644 --- a/test/google_navigation_flutter_test.dart +++ b/test/google_navigation_flutter_test.dart @@ -1046,7 +1046,51 @@ void main() { // Add marker final List markersOut = await GoogleMapsNavigationPlatform .instance.viewAPI - .addMarkers(viewId: 0, markerOptions: [optionsIn]); + .addMarkers(viewId: 0, markerOptions: <({ + MarkerOptions options, + String? markerId + })>[(options: optionsIn, markerId: null)]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addMarkers(captureAny, captureAny)); + final List markersInMessage = + result.captured[1] as List; + + // Verify message and response marker options + expect(markerIn.markerId, markersInMessage[0]?.markerId); + expect(optionsIn, markersInMessage[0]?.options.toMarkerOptions()); + expect(optionsIn, markersOut[0]!.options); + }); + + test('add marker with custom id', () async { + // Create options + const MarkerOptions optionsIn = MarkerOptions( + alpha: 0.5, + anchor: MarkerAnchor(u: 0.1, v: 0.2), + draggable: true, + flat: true, + consumeTapEvents: true, + position: LatLng(latitude: 50, longitude: 60), + rotation: 70, + infoWindow: InfoWindow(title: 'Title', snippet: 'Snippet'), + zIndex: 2); + + // Mock api response + final MarkerDto markerIn = MarkerDto( + markerId: 'CustomMarkerID_0', + options: optionsIn.toDto(), + ); + when(viewMockApi.addMarkers(any, any)) + .thenAnswer((Invocation _) => [markerIn]); + + // Add marker + final List markersOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .addMarkers(viewId: 0, markerOptions: <({ + MarkerOptions options, + String? markerId + })>[(options: optionsIn, markerId: 'CustomMarkerID_0')]); // Verify correct message sent from view api final VerificationResult result = @@ -1197,9 +1241,56 @@ void main() { .thenAnswer((Invocation _) => [polygonIn]); // Add polygon - final List polygonsOut = - await GoogleMapsNavigationPlatform.instance.viewAPI.addPolygons( - viewId: 0, polygonOptions: [optionsIn]); + final List polygonsOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .addPolygons(viewId: 0, polygonOptions: <({ + PolygonOptions options, + String? polygonId + })>[(options: optionsIn, polygonId: null)]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addPolygons(captureAny, captureAny)); + final List polygonsInMessage = + result.captured[1] as List; + + // Verify message and response polygon options + expect(polygonIn.polygonId, polygonsInMessage[0]?.polygonId); + expect(optionsIn, polygonsInMessage[0]?.options.toPolygonOptions()); + expect(optionsIn, polygonsOut[0]!.options); + }); + + test('add polygon with custom id', () async { + // Create options + const PolygonOptions optionsIn = PolygonOptions( + points: [ + LatLng(latitude: 40.0, longitude: 50.0) + ], + holes: >[ + [LatLng(latitude: 60.0, longitude: 70.0)] + ], + clickable: true, + fillColor: Colors.amber, + geodesic: true, + strokeColor: Colors.cyan, + strokeWidth: 4, + zIndex: 3); + + // Mock api response + final PolygonDto polygonIn = PolygonDto( + polygonId: 'CustomPolygonID_0', + options: optionsIn.toDto(), + ); + when(viewMockApi.addPolygons(any, any)) + .thenAnswer((Invocation _) => [polygonIn]); + + // Add polygon + final List polygonsOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .addPolygons(viewId: 0, polygonOptions: <({ + PolygonOptions options, + String? polygonId + })>[(options: optionsIn, polygonId: 'CustomPolygonID_0')]); // Verify correct message sent from view api final VerificationResult result = @@ -1283,6 +1374,364 @@ void main() { expect(viewId, 0); }); }); + + group('Polylines', () { + test('get polylines', () async { + // Create polyline + const Polyline polyline = + Polyline(polylineId: 'Polyline_0', options: PolylineOptions()); + + // Mock api response + final PolylineDto messagePolyline = PolylineDto( + polylineId: 'Polyline_0', options: polyline.options.toDto()); + when(viewMockApi.getPolylines(any)) + .thenAnswer((Invocation _) => [messagePolyline]); + + // Get polylines + final List polylinesOut = + await GoogleMapsNavigationPlatform.instance.viewAPI + .getPolylines(viewId: 0); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.getPolylines(captureAny)); + final int viewId = result.captured[0] as int; + + // Verify response polyline options + expect(viewId, 0); + expect(polyline.options, polylinesOut[0]!.options); + }); + + test('add polyline', () async { + // Create options + const PolylineOptions optionsIn = PolylineOptions( + points: [ + LatLng(latitude: 40.0, longitude: 50.0), + LatLng(latitude: 41.0, longitude: 51.0) + ], + clickable: true, + strokeColor: Colors.blue, + strokeJointType: StrokeJointType.round, + geodesic: true, + strokeWidth: 5, + visible: true, + zIndex: 3); + + // Mock api response + final PolylineDto polylineIn = + PolylineDto(polylineId: 'Polyline_0', options: optionsIn.toDto()); + when(viewMockApi.addPolylines(any, any)) + .thenAnswer((Invocation _) => [polylineIn]); + + // Add polyline + final List polylinesOut = + await GoogleMapsNavigationPlatform.instance.viewAPI.addPolylines( + viewId: 0, polylineOptions: <({ + PolylineOptions options, + String? polylineId + })>[(options: optionsIn, polylineId: null)]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addPolylines(captureAny, captureAny)); + final List polylinesInMessage = + result.captured[1] as List; + + // Verify message and response polyline options + expect(polylineIn.polylineId, polylinesInMessage[0]?.polylineId); + expect(optionsIn, polylinesInMessage[0]?.options.toPolylineOptions()); + expect(optionsIn, polylinesOut[0]!.options); + }); + + test('add polyline with custom id', () async { + // Create options + const PolylineOptions optionsIn = PolylineOptions( + points: [ + LatLng(latitude: 40.0, longitude: 50.0), + LatLng(latitude: 41.0, longitude: 51.0) + ], + clickable: true, + strokeColor: Colors.blue, + strokeJointType: StrokeJointType.round, + geodesic: true, + strokeWidth: 5, + visible: true, + zIndex: 3); + + // Mock api response + final PolylineDto polylineIn = PolylineDto( + polylineId: 'CustomPolylineID_0', + options: optionsIn.toDto(), + ); + when(viewMockApi.addPolylines(any, any)) + .thenAnswer((Invocation _) => [polylineIn]); + + // Add polyline + final List polylinesOut = + await GoogleMapsNavigationPlatform.instance.viewAPI.addPolylines( + viewId: 0, polylineOptions: <({ + PolylineOptions options, + String? polylineId + })>[(options: optionsIn, polylineId: 'CustomPolylineID_0')]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addPolylines(captureAny, captureAny)); + final List polylinesInMessage = + result.captured[1] as List; + + // Verify message and response polyline options + expect(polylineIn.polylineId, polylinesInMessage[0]?.polylineId); + expect(optionsIn, polylinesInMessage[0]?.options.toPolylineOptions()); + expect(optionsIn, polylinesOut[0]!.options); + }); + + test('update polyline', () async { + // Create polyline + const Polyline polyline = + Polyline(polylineId: 'Polyline_0', options: PolylineOptions()); + + // Mock api response + final PolylineDto messagePolyline = PolylineDto( + polylineId: 'Polyline_0', options: polyline.options.toDto()); + when(viewMockApi.updatePolylines(any, any)) + .thenAnswer((Invocation _) => [messagePolyline]); + + // Edit polyline + final List polylinesOut = + await GoogleMapsNavigationPlatform.instance.viewAPI + .updatePolylines(viewId: 0, polylines: [polyline]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.updatePolylines(captureAny, captureAny)); + final List polylinesInMessage = + result.captured[1] as List; + + // Verify message and response polyline options + expect(polyline.polylineId, polylinesInMessage[0]?.polylineId); + expect(polyline.options, + polylinesInMessage[0]?.options.toPolylineOptions()); + expect(polyline.options, polylinesOut[0]!.options); + }); + + test('remove polyline', () async { + // Create polyline + const Polyline polyline = + Polyline(polylineId: 'Polyline_0', options: PolylineOptions()); + + // Mock api response + when(viewMockApi.removePolylines(any, any)) + .thenAnswer((Invocation _) async => ()); + + // Remove polyline + await GoogleMapsNavigationPlatform.instance.viewAPI + .removePolylines(viewId: 0, polylines: [polyline]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.removePolylines(captureAny, captureAny)); + final List polylinesInMessage = + result.captured[1] as List; + + // Verify message + expect(polyline.polylineId, polylinesInMessage[0]?.polylineId); + }); + + test('clear polylines', () async { + // Mock api response + when(viewMockApi.clearPolylines(any)) + .thenAnswer((Invocation _) async => ()); + + // Clear polylines + await GoogleMapsNavigationPlatform.instance.viewAPI + .clearPolylines(viewId: 0); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.clearPolylines(captureAny)); + final int viewId = result.captured[0] as int; + + // Verify message + expect(viewId, 0); + }); + }); + + group('Circles', () { + test('get circles', () async { + // Create circle + const Circle circle = + Circle(circleId: 'Circle_0', options: CircleOptions()); + + // Mock api response + final CircleDto messageCircle = + CircleDto(circleId: 'Circle_0', options: circle.options.toDto()); + when(viewMockApi.getCircles(any)) + .thenAnswer((Invocation _) => [messageCircle]); + + // Get circles + final List circlesOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .getCircles(viewId: 0); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.getCircles(captureAny)); + final int viewId = result.captured[0] as int; + + // Verify response circle options + expect(viewId, 0); + expect(circle.options, circlesOut[0]!.options); + }); + + test('add circle', () async { + // Create options + const CircleOptions optionsIn = CircleOptions( + position: LatLng(latitude: 40.0, longitude: 50.0), + clickable: true, + fillColor: Colors.green, + radius: 1000.0, + strokeColor: Colors.red, + strokeWidth: 3, + visible: true, + zIndex: 2); + + // Mock api response + final CircleDto circleIn = + CircleDto(circleId: 'Circle_0', options: optionsIn.toDto()); + when(viewMockApi.addCircles(any, any)) + .thenAnswer((Invocation _) => [circleIn]); + + // Add circle + final List circlesOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .addCircles(viewId: 0, circleOptions: <({ + CircleOptions options, + String? circleId + })>[(options: optionsIn, circleId: null)]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addCircles(captureAny, captureAny)); + final List circlesInMessage = + result.captured[1] as List; + + // Verify message and response circle options + expect(circleIn.circleId, circlesInMessage[0]?.circleId); + expect(optionsIn, circlesInMessage[0]?.options.toCircleOptions()); + expect(optionsIn, circlesOut[0]!.options); + }); + + test('add circle with custom id', () async { + // Create options + const CircleOptions optionsIn = CircleOptions( + position: LatLng(latitude: 40.0, longitude: 50.0), + clickable: true, + fillColor: Colors.green, + radius: 1000.0, + strokeColor: Colors.red, + strokeWidth: 3, + visible: true, + zIndex: 2); + + // Mock api response + final CircleDto circleIn = CircleDto( + circleId: 'CustomCircleID_0', + options: optionsIn.toDto(), + ); + when(viewMockApi.addCircles(any, any)) + .thenAnswer((Invocation _) => [circleIn]); + + // Add circle + final List circlesOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .addCircles(viewId: 0, circleOptions: <({ + CircleOptions options, + String? circleId + })>[(options: optionsIn, circleId: 'CustomCircleID_0')]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.addCircles(captureAny, captureAny)); + final List circlesInMessage = + result.captured[1] as List; + + // Verify message and response circle options + expect(circleIn.circleId, circlesInMessage[0]?.circleId); + expect(optionsIn, circlesInMessage[0]?.options.toCircleOptions()); + expect(optionsIn, circlesOut[0]!.options); + }); + + test('update circle', () async { + // Create circle + const Circle circle = + Circle(circleId: 'Circle_0', options: CircleOptions()); + + // Mock api response + final CircleDto messageCircle = + CircleDto(circleId: 'Circle_0', options: circle.options.toDto()); + when(viewMockApi.updateCircles(any, any)) + .thenAnswer((Invocation _) => [messageCircle]); + + // Edit circle + final List circlesOut = await GoogleMapsNavigationPlatform + .instance.viewAPI + .updateCircles(viewId: 0, circles: [circle]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.updateCircles(captureAny, captureAny)); + final List circlesInMessage = + result.captured[1] as List; + + // Verify message and response circle options + expect(circle.circleId, circlesInMessage[0]?.circleId); + expect( + circle.options, circlesInMessage[0]?.options.toCircleOptions()); + expect(circle.options, circlesOut[0]!.options); + }); + + test('remove circle', () async { + // Create circle + const Circle circle = + Circle(circleId: 'Circle_0', options: CircleOptions()); + + // Mock api response + when(viewMockApi.removeCircles(any, any)) + .thenAnswer((Invocation _) async => ()); + + // Remove circle + await GoogleMapsNavigationPlatform.instance.viewAPI + .removeCircles(viewId: 0, circles: [circle]); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.removeCircles(captureAny, captureAny)); + final List circlesInMessage = + result.captured[1] as List; + + // Verify message + expect(circle.circleId, circlesInMessage[0]?.circleId); + }); + + test('clear circles', () async { + // Mock api response + when(viewMockApi.clearCircles(any)) + .thenAnswer((Invocation _) async => ()); + + // Clear circles + await GoogleMapsNavigationPlatform.instance.viewAPI + .clearCircles(viewId: 0); + + // Verify correct message sent from view api + final VerificationResult result = + verify(viewMockApi.clearCircles(captureAny)); + final int viewId = result.captured[0] as int; + + // Verify message + expect(viewId, 0); + }); + }); }); } }