From c147de5fa4ae668cf1695e915ccafa12ca57e1d2 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 10 Aug 2021 18:14:54 -0700 Subject: [PATCH 01/17] Add _latLngToPixel convert function --- .../lib/src/convert.dart | 31 +++++++++++++++++++ .../lib/src/google_maps_controller.dart | 8 +---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 2e71c795ff0e..b251131b9c0c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -416,6 +416,7 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { } // original JS by: Byron Singh (https://stackoverflow.com/a/30541162) +// (This is the reverse of [_latLngToPixel]) gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) { final bounds = map.bounds; final projection = map.projection; @@ -441,3 +442,33 @@ gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) { return projection.fromPointToLatLng!(point)!; } + +// original JS by: Krasimir (https://krasimirtsonev.com/blog/article/google-maps-api-v3-convert-latlng-object-to-actual-pixels-point-object) +// (This is the reverse of [_pixelToLatLng]) +gmaps.Point _latLngToPixel(gmaps.GMap map, gmaps.LatLng coords) { + final zoom = map.zoom; + final bounds = map.bounds; + final projection = map.projection; + + assert( + bounds != null, 'Map Bounds required to compute screen x/y of LatLng.'); + assert(projection != null, + 'Map Projection required to compute screen x/y of LatLng.'); + assert(zoom != null, + 'Current map zoom level required to compute screen x/y of LatLng.'); + + final ne = bounds!.northEast; + final sw = bounds.southWest; + + final topRight = projection!.fromLatLngToPoint!(ne)!; + final bottomLeft = projection.fromLatLngToPoint!(sw)!; + + final scale = 1 << (zoom!.toInt()); // 2 ^ zoom + + final worldPoint = projection.fromLatLngToPoint!(coords)!; + + return gmaps.Point( + ((worldPoint.x! - bottomLeft.x!) * scale).toInt(), + ((worldPoint.y! - topRight.y!) * scale).toInt(), + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 226268270579..9fbad945d445 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -292,14 +292,8 @@ class GoogleMapController { Future getScreenCoordinate(LatLng latLng) async { assert(_googleMap != null, 'Cannot get the screen coordinates with a null map.'); - assert(_googleMap!.projection != null, - 'Cannot compute screen coordinate with a null map or projection.'); - final point = - _googleMap!.projection!.fromLatLngToPoint!(_latLngToGmLatLng(latLng))!; - - assert(point.x != null && point.y != null, - 'The x and y of a ScreenCoordinate cannot be null.'); + final point = _latLngToPixel(_googleMap!, _latLngToGmLatLng(latLng)); return ScreenCoordinate(x: point.x!.toInt(), y: point.y!.toInt()); } From 2f2ec6252c87c779ad5c98ec37a14c50a74a58b8 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Fri, 27 Aug 2021 18:22:49 -0700 Subject: [PATCH 02/17] Bump google_maps dep to the one that fixed the original issue. --- .../google_maps_flutter/google_maps_flutter_web/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 82605f8fd070..5910af9ac8db 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: flutter_web_plugins: sdk: flutter google_maps_flutter_platform_interface: ^2.0.1 - google_maps: ^5.1.0 + google_maps: ^5.2.0 meta: ^1.3.0 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 From c19e5cf05a0796486f4b5d920aa708740605840c Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Mon, 30 Aug 2021 12:00:17 -0700 Subject: [PATCH 03/17] Add test for the projection of the map in a known-size widget. --- .../integration_test/projection_test.dart | 122 ++++++++++++++++++ .../example/pubspec.yaml | 2 + 2 files changed, 124 insertions(+) create mode 100644 packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart new file mode 100644 index 000000000000..99d974eb4047 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// These tests render an app with a small map widget, and use its map controller +// to compute values of the default projection. + +// (Tests methods that can't be mocked in `google_maps_controller_test.dart`) + +import 'dart:async'; +import 'dart:html' as html; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps/google_maps.dart' as gmaps; +import 'package:google_maps_flutter/google_maps_flutter.dart' show GoogleMap, GoogleMapController; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' hide GoogleMapController; +import 'package:integration_test/integration_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +// This value is used when comparing long~num, like +// LatLng values. +const _acceptableDelta = 0.0000000001; + +/// Test Google Map Controller +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('initializes', () { + final LatLng center = LatLng(43.3078, -5.6958); + final Size size = Size(320, 240); + + late Completer controllerCompleter; + late void Function(GoogleMapController) onMapCreated; + + setUp(() { + controllerCompleter = Completer(); + onMapCreated = (GoogleMapController mapController) { + controllerCompleter.complete(mapController); + }; + }); + + testWidgets('target of map is in center of widget', (WidgetTester tester) async { + await tester.pumpWidget( + CenteredMap( + initialCamera: CameraPosition( + target: center, + zoom: 14, + ), + size: size, + onMapCreated: onMapCreated, + ), + Duration(milliseconds: 500), + ); + + final GoogleMapController controller = await controllerCompleter.future; + + // This is needed to kick-off the rendering of the Map flutter widget + await tester.pumpAndSettle(Duration(milliseconds: 500)); + + // Find the element of the map, and attach a Resize observer to it... + + + // This is needed to let the JS map do its thing + await Future.delayed(Duration(milliseconds: 5000)); + + final ScreenCoordinate coords = await controller.getScreenCoordinate(center); + + print(coords); + + // final ScreenCoordinate coords = (await tester.runAsync(() { + // print('About to wait...'); + // return Future.delayed(Duration(seconds: 10), () { + // print('10 seconds have passed...'); + // return controller.getScreenCoordinate(center); + // }); + // }, additionalTime: Duration(seconds: 10)))!; + + expect(coords.x, size.width / 2); + expect(coords.y, size.height / 2); + }); + }); +} + +/// Renders a Map widget centered on the screen. +/// This depends in `package:google_maps_flutter` to work. +class CenteredMap extends StatelessWidget { + + const CenteredMap({ + required this.initialCamera, + required this.size, + required this.onMapCreated, + Key? key + }) : super(key: key); + + /// A function that receives the [GoogleMapController] of the Map widget once initialized. + final void Function(GoogleMapController)? onMapCreated; + /// The size of the rendered map widget. + final Size size; + /// The initial camera position (center + zoom level) of the Map widget. + final CameraPosition initialCamera; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox.fromSize( + size: size, + child: GoogleMap( + initialCameraPosition: initialCamera, + onMapCreated: onMapCreated, + ), + ), + ), + ), + ); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index b0ac9910afc9..11fc559faf53 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -7,6 +7,8 @@ environment: flutter: ">=2.1.0" dependencies: + google_maps_flutter: + path: ../../google_maps_flutter google_maps_flutter_web: path: ../ flutter: From 6ef4084f5424412ac17bc7b0fff369172c9278db Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 31 Aug 2021 14:29:32 -0700 Subject: [PATCH 04/17] Update dependencies and mock files. --- .../google_maps_controller_test.mocks.dart | 77 ++++++----- .../google_maps_plugin_test.mocks.dart | 120 +++++++++++------- .../example/pubspec.yaml | 4 +- 3 files changed, 118 insertions(+), 83 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart index 47933285b208..af8ed5420a0c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart @@ -2,26 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Mocks generated by Mockito 5.0.2 from annotations +// Mocks generated by Mockito 5.0.15 from annotations // in google_maps_flutter_web_integration_tests/integration_test/google_maps_controller_test.dart. // Do not manually edit this file. -import 'package:google_maps/src/generated/google_maps_core.js.g.dart' as _i2; -import 'package:google_maps_flutter_platform_interface/src/types/circle.dart' +import 'package:google_maps/google_maps.dart' as _i2; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i4; -import 'package:google_maps_flutter_platform_interface/src/types/marker.dart' - as _i7; -import 'package:google_maps_flutter_platform_interface/src/types/polygon.dart' - as _i5; -import 'package:google_maps_flutter_platform_interface/src/types/polyline.dart' - as _i6; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis -class _FakeGMap extends _i1.Fake implements _i2.GMap {} +class _FakeGMap_0 extends _i1.Fake implements _i2.GMap {} /// A class which mocks [CirclesController]. /// @@ -34,7 +33,7 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { as Map<_i4.CircleId, _i3.CircleController>); @override _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap), - returnValue: _FakeGMap()) as _i2.GMap); + returnValue: _FakeGMap_0()) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(Invocation.setter(#googleMap, _googleMap), @@ -62,6 +61,8 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { void bindToMap(int? mapId, _i2.GMap? googleMap) => super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]), returnValueForMissingStub: null); + @override + String toString() => super.toString(); } /// A class which mocks [PolygonsController]. @@ -70,13 +71,13 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController { class MockPolygonsController extends _i1.Mock implements _i3.PolygonsController { @override - Map<_i5.PolygonId, _i3.PolygonController> get polygons => + Map<_i4.PolygonId, _i3.PolygonController> get polygons => (super.noSuchMethod(Invocation.getter(#polygons), - returnValue: <_i5.PolygonId, _i3.PolygonController>{}) - as Map<_i5.PolygonId, _i3.PolygonController>); + returnValue: <_i4.PolygonId, _i3.PolygonController>{}) + as Map<_i4.PolygonId, _i3.PolygonController>); @override _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap), - returnValue: _FakeGMap()) as _i2.GMap); + returnValue: _FakeGMap_0()) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(Invocation.setter(#googleMap, _googleMap), @@ -89,21 +90,23 @@ class MockPolygonsController extends _i1.Mock super.noSuchMethod(Invocation.setter(#mapId, _mapId), returnValueForMissingStub: null); @override - void addPolygons(Set<_i5.Polygon>? polygonsToAdd) => + void addPolygons(Set<_i4.Polygon>? polygonsToAdd) => super.noSuchMethod(Invocation.method(#addPolygons, [polygonsToAdd]), returnValueForMissingStub: null); @override - void changePolygons(Set<_i5.Polygon>? polygonsToChange) => + void changePolygons(Set<_i4.Polygon>? polygonsToChange) => super.noSuchMethod(Invocation.method(#changePolygons, [polygonsToChange]), returnValueForMissingStub: null); @override - void removePolygons(Set<_i5.PolygonId>? polygonIdsToRemove) => super + void removePolygons(Set<_i4.PolygonId>? polygonIdsToRemove) => super .noSuchMethod(Invocation.method(#removePolygons, [polygonIdsToRemove]), returnValueForMissingStub: null); @override void bindToMap(int? mapId, _i2.GMap? googleMap) => super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]), returnValueForMissingStub: null); + @override + String toString() => super.toString(); } /// A class which mocks [PolylinesController]. @@ -112,13 +115,13 @@ class MockPolygonsController extends _i1.Mock class MockPolylinesController extends _i1.Mock implements _i3.PolylinesController { @override - Map<_i6.PolylineId, _i3.PolylineController> get lines => + Map<_i4.PolylineId, _i3.PolylineController> get lines => (super.noSuchMethod(Invocation.getter(#lines), - returnValue: <_i6.PolylineId, _i3.PolylineController>{}) - as Map<_i6.PolylineId, _i3.PolylineController>); + returnValue: <_i4.PolylineId, _i3.PolylineController>{}) + as Map<_i4.PolylineId, _i3.PolylineController>); @override _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap), - returnValue: _FakeGMap()) as _i2.GMap); + returnValue: _FakeGMap_0()) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(Invocation.setter(#googleMap, _googleMap), @@ -131,21 +134,23 @@ class MockPolylinesController extends _i1.Mock super.noSuchMethod(Invocation.setter(#mapId, _mapId), returnValueForMissingStub: null); @override - void addPolylines(Set<_i6.Polyline>? polylinesToAdd) => + void addPolylines(Set<_i4.Polyline>? polylinesToAdd) => super.noSuchMethod(Invocation.method(#addPolylines, [polylinesToAdd]), returnValueForMissingStub: null); @override - void changePolylines(Set<_i6.Polyline>? polylinesToChange) => super + void changePolylines(Set<_i4.Polyline>? polylinesToChange) => super .noSuchMethod(Invocation.method(#changePolylines, [polylinesToChange]), returnValueForMissingStub: null); @override - void removePolylines(Set<_i6.PolylineId>? polylineIdsToRemove) => super + void removePolylines(Set<_i4.PolylineId>? polylineIdsToRemove) => super .noSuchMethod(Invocation.method(#removePolylines, [polylineIdsToRemove]), returnValueForMissingStub: null); @override void bindToMap(int? mapId, _i2.GMap? googleMap) => super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]), returnValueForMissingStub: null); + @override + String toString() => super.toString(); } /// A class which mocks [MarkersController]. @@ -153,13 +158,13 @@ class MockPolylinesController extends _i1.Mock /// See the documentation for Mockito's code generation for more information. class MockMarkersController extends _i1.Mock implements _i3.MarkersController { @override - Map<_i7.MarkerId, _i3.MarkerController> get markers => + Map<_i4.MarkerId, _i3.MarkerController> get markers => (super.noSuchMethod(Invocation.getter(#markers), - returnValue: <_i7.MarkerId, _i3.MarkerController>{}) - as Map<_i7.MarkerId, _i3.MarkerController>); + returnValue: <_i4.MarkerId, _i3.MarkerController>{}) + as Map<_i4.MarkerId, _i3.MarkerController>); @override _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap), - returnValue: _FakeGMap()) as _i2.GMap); + returnValue: _FakeGMap_0()) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(Invocation.setter(#googleMap, _googleMap), @@ -172,31 +177,33 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController { super.noSuchMethod(Invocation.setter(#mapId, _mapId), returnValueForMissingStub: null); @override - void addMarkers(Set<_i7.Marker>? markersToAdd) => + void addMarkers(Set<_i4.Marker>? markersToAdd) => super.noSuchMethod(Invocation.method(#addMarkers, [markersToAdd]), returnValueForMissingStub: null); @override - void changeMarkers(Set<_i7.Marker>? markersToChange) => + void changeMarkers(Set<_i4.Marker>? markersToChange) => super.noSuchMethod(Invocation.method(#changeMarkers, [markersToChange]), returnValueForMissingStub: null); @override - void removeMarkers(Set<_i7.MarkerId>? markerIdsToRemove) => + void removeMarkers(Set<_i4.MarkerId>? markerIdsToRemove) => super.noSuchMethod(Invocation.method(#removeMarkers, [markerIdsToRemove]), returnValueForMissingStub: null); @override - void showMarkerInfoWindow(_i7.MarkerId? markerId) => + void showMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod(Invocation.method(#showMarkerInfoWindow, [markerId]), returnValueForMissingStub: null); @override - void hideMarkerInfoWindow(_i7.MarkerId? markerId) => + void hideMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod(Invocation.method(#hideMarkerInfoWindow, [markerId]), returnValueForMissingStub: null); @override - bool isInfoWindowShown(_i7.MarkerId? markerId) => + bool isInfoWindowShown(_i4.MarkerId? markerId) => (super.noSuchMethod(Invocation.method(#isInfoWindowShown, [markerId]), returnValue: false) as bool); @override void bindToMap(int? mapId, _i2.GMap? googleMap) => super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]), returnValueForMissingStub: null); + @override + String toString() => super.toString(); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart index 43150f63ef93..01908ce777e7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart @@ -2,41 +2,34 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Mocks generated by Mockito 5.0.2 from annotations +// Mocks generated by Mockito 5.0.15 from annotations // in google_maps_flutter_web_integration_tests/integration_test/google_maps_plugin_test.dart. // Do not manually edit this file. -import 'dart:async' as _i5; +import 'dart:async' as _i2; -import 'package:google_maps_flutter_platform_interface/src/events/map_event.dart' - as _i6; -import 'package:google_maps_flutter_platform_interface/src/types/camera.dart' - as _i7; -import 'package:google_maps_flutter_platform_interface/src/types/circle_updates.dart' - as _i8; -import 'package:google_maps_flutter_platform_interface/src/types/location.dart' - as _i2; -import 'package:google_maps_flutter_platform_interface/src/types/marker.dart' - as _i12; -import 'package:google_maps_flutter_platform_interface/src/types/marker_updates.dart' - as _i11; -import 'package:google_maps_flutter_platform_interface/src/types/polygon_updates.dart' - as _i9; -import 'package:google_maps_flutter_platform_interface/src/types/polyline_updates.dart' - as _i10; -import 'package:google_maps_flutter_platform_interface/src/types/screen_coordinate.dart' +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i3; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis -class _FakeLatLngBounds extends _i1.Fake implements _i2.LatLngBounds {} +class _FakeStreamController_0 extends _i1.Fake + implements _i2.StreamController {} -class _FakeScreenCoordinate extends _i1.Fake implements _i3.ScreenCoordinate {} +class _FakeLatLngBounds_1 extends _i1.Fake implements _i3.LatLngBounds {} -class _FakeLatLng extends _i1.Fake implements _i2.LatLng {} +class _FakeScreenCoordinate_2 extends _i1.Fake implements _i3.ScreenCoordinate { +} + +class _FakeLatLng_3 extends _i1.Fake implements _i3.LatLng {} /// A class which mocks [GoogleMapController]. /// @@ -44,63 +37,98 @@ class _FakeLatLng extends _i1.Fake implements _i2.LatLng {} class MockGoogleMapController extends _i1.Mock implements _i4.GoogleMapController { @override - _i5.Stream<_i6.MapEvent> get events => + _i2.StreamController<_i3.MapEvent> get stream => + (super.noSuchMethod(Invocation.getter(#stream), + returnValue: _FakeStreamController_0<_i3.MapEvent>()) + as _i2.StreamController<_i3.MapEvent>); + @override + _i2.Stream<_i3.MapEvent> get events => (super.noSuchMethod(Invocation.getter(#events), - returnValue: Stream<_i6.MapEvent>.empty()) - as _i5.Stream<_i6.MapEvent>); + returnValue: Stream<_i3.MapEvent>.empty()) + as _i2.Stream<_i3.MapEvent>); + @override + bool get isInitialized => + (super.noSuchMethod(Invocation.getter(#isInitialized), returnValue: false) + as bool); + @override + void debugSetOverrides( + {_i4.DebugCreateMapFunction? createMap, + _i4.MarkersController? markers, + _i4.CirclesController? circles, + _i4.PolygonsController? polygons, + _i4.PolylinesController? polylines}) => + super.noSuchMethod( + Invocation.method(#debugSetOverrides, [], { + #createMap: createMap, + #markers: markers, + #circles: circles, + #polygons: polygons, + #polylines: polylines + }), + returnValueForMissingStub: null); + @override + void init() => super.noSuchMethod(Invocation.method(#init, []), + returnValueForMissingStub: null); @override void updateRawOptions(Map? optionsUpdate) => super.noSuchMethod(Invocation.method(#updateRawOptions, [optionsUpdate]), returnValueForMissingStub: null); @override - _i5.Future<_i2.LatLngBounds> getVisibleRegion() => - (super.noSuchMethod(Invocation.method(#getVisibleRegion, []), - returnValue: Future.value(_FakeLatLngBounds())) - as _i5.Future<_i2.LatLngBounds>); + _i2.Future<_i3.LatLngBounds> getVisibleRegion() => (super.noSuchMethod( + Invocation.method(#getVisibleRegion, []), + returnValue: Future<_i3.LatLngBounds>.value(_FakeLatLngBounds_1())) + as _i2.Future<_i3.LatLngBounds>); @override - _i5.Future<_i3.ScreenCoordinate> getScreenCoordinate(_i2.LatLng? latLng) => + _i2.Future<_i3.ScreenCoordinate> getScreenCoordinate(_i3.LatLng? latLng) => (super.noSuchMethod(Invocation.method(#getScreenCoordinate, [latLng]), - returnValue: Future.value(_FakeScreenCoordinate())) - as _i5.Future<_i3.ScreenCoordinate>); + returnValue: + Future<_i3.ScreenCoordinate>.value(_FakeScreenCoordinate_2())) + as _i2.Future<_i3.ScreenCoordinate>); @override - _i5.Future<_i2.LatLng> getLatLng(_i3.ScreenCoordinate? screenCoordinate) => + _i2.Future<_i3.LatLng> getLatLng(_i3.ScreenCoordinate? screenCoordinate) => (super.noSuchMethod(Invocation.method(#getLatLng, [screenCoordinate]), - returnValue: Future.value(_FakeLatLng())) as _i5.Future<_i2.LatLng>); + returnValue: Future<_i3.LatLng>.value(_FakeLatLng_3())) + as _i2.Future<_i3.LatLng>); @override - _i5.Future moveCamera(_i7.CameraUpdate? cameraUpdate) => + _i2.Future moveCamera(_i3.CameraUpdate? cameraUpdate) => (super.noSuchMethod(Invocation.method(#moveCamera, [cameraUpdate]), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i2.Future); @override - _i5.Future getZoomLevel() => + _i2.Future getZoomLevel() => (super.noSuchMethod(Invocation.method(#getZoomLevel, []), - returnValue: Future.value(0.0)) as _i5.Future); + returnValue: Future.value(0.0)) as _i2.Future); @override - void updateCircles(_i8.CircleUpdates? updates) => + void updateCircles(_i3.CircleUpdates? updates) => super.noSuchMethod(Invocation.method(#updateCircles, [updates]), returnValueForMissingStub: null); @override - void updatePolygons(_i9.PolygonUpdates? updates) => + void updatePolygons(_i3.PolygonUpdates? updates) => super.noSuchMethod(Invocation.method(#updatePolygons, [updates]), returnValueForMissingStub: null); @override - void updatePolylines(_i10.PolylineUpdates? updates) => + void updatePolylines(_i3.PolylineUpdates? updates) => super.noSuchMethod(Invocation.method(#updatePolylines, [updates]), returnValueForMissingStub: null); @override - void updateMarkers(_i11.MarkerUpdates? updates) => + void updateMarkers(_i3.MarkerUpdates? updates) => super.noSuchMethod(Invocation.method(#updateMarkers, [updates]), returnValueForMissingStub: null); @override - void showInfoWindow(_i12.MarkerId? markerId) => + void showInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod(Invocation.method(#showInfoWindow, [markerId]), returnValueForMissingStub: null); @override - void hideInfoWindow(_i12.MarkerId? markerId) => + void hideInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod(Invocation.method(#hideInfoWindow, [markerId]), returnValueForMissingStub: null); @override - bool isInfoWindowShown(_i12.MarkerId? markerId) => + bool isInfoWindowShown(_i3.MarkerId? markerId) => (super.noSuchMethod(Invocation.method(#isInfoWindowShown, [markerId]), returnValue: false) as bool); + @override + void dispose() => super.noSuchMethod(Invocation.method(#dispose, []), + returnValueForMissingStub: null); + @override + String toString() => super.toString(); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 11fc559faf53..8416c502328f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -7,7 +7,7 @@ environment: flutter: ">=2.1.0" dependencies: - google_maps_flutter: + google_maps_flutter: # Used for projection_test.dart path: ../../google_maps_flutter google_maps_flutter_web: path: ../ @@ -15,7 +15,7 @@ dependencies: sdk: flutter dev_dependencies: - build_runner: ^1.11.0 + build_runner: ^2.1.1 google_maps: ^5.1.0 http: ^0.13.0 mockito: ^5.0.0 From a475d90c1acd7d59edf23228159d7056ea540585 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 31 Aug 2021 14:57:12 -0700 Subject: [PATCH 05/17] Wait until the first `onTilesloaded` to call `onPlatformViewCreated`. --- .../google_maps_plugin_test.dart | 63 +++++++++++++------ .../lib/src/google_maps_controller.dart | 38 ++++++++++- .../lib/src/google_maps_flutter_web.dart | 13 +++- 3 files changed, 89 insertions(+), 25 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart index 2de431a5445e..8c55f01f9c2f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart @@ -28,16 +28,18 @@ void main() { group('GoogleMapsPlugin', () { late MockGoogleMapController controller; late GoogleMapsPlugin plugin; - int? reportedMapId; + late Completer reportedMapIdCompleter; + int numberOnPlatformViewCreatedCalls = 0; void onPlatformViewCreated(int id) { - reportedMapId = id; + reportedMapIdCompleter.complete(id); + numberOnPlatformViewCreatedCalls++; } setUp(() { controller = MockGoogleMapController(); plugin = GoogleMapsPlugin(); - reportedMapId = null; + reportedMapIdCompleter = Completer(); }); group('init/dispose', () { @@ -52,12 +54,6 @@ void main() { plugin.debugSetMapById({0: controller}); }); - testWidgets('init initializes controller', (WidgetTester tester) async { - await plugin.init(0); - - verify(controller.init()); - }); - testWidgets('cannot call methods after dispose', (WidgetTester tester) async { plugin.dispose(mapId: 0); @@ -95,17 +91,17 @@ void main() { reason: 'view type should contain the mapId passed when creating the map.', ); - expect( - reportedMapId, - testMapId, - reason: 'Should call onPlatformViewCreated with the mapId', - ); expect(cache, contains(testMapId)); expect( cache[testMapId], isNotNull, reason: 'cached controller cannot be null.', ); + expect( + cache[testMapId]!.isInitialized, + isTrue, + reason: 'buildView calls init on the controller' + ); }); testWidgets('returns cached instance if it already exists', @@ -121,14 +117,42 @@ void main() { ); expect(widget, equals(expected)); + }); + + testWidgets( + 'asynchronously reports onPlatformViewCreated the first time it happens', + (WidgetTester tester) async { + final Map cache = {}; + plugin.debugSetMapById(cache); + + plugin.buildView( + testMapId, + onPlatformViewCreated, + initialCameraPosition: initialCameraPosition, + ); + + // Simulate Google Maps JS SDK being "ready" + cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId)); + expect( - reportedMapId, - isNull, - reason: - 'onPlatformViewCreated should not be called when returning a cached controller', + cache[testMapId]!.isInitialized, + isTrue, + reason: 'buildView calls init on the controller' + ); + expect( + await reportedMapIdCompleter.future, + testMapId, + reason: 'Should call onPlatformViewCreated with the mapId', + ); + + // Fire repeated event again... + cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId)); + expect( + numberOnPlatformViewCreatedCalls, + equals(1), + reason: 'Should not call onPlatformViewCreated for the same controller multiple times', ); }); - }); group('setMapStyles', () { String mapStyle = '''[{ @@ -428,6 +452,7 @@ void main() { }); }); }); +}); } class _OtherMapEvent extends MapEvent { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 9fbad945d445..bd14cf13a47e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -53,6 +53,10 @@ class GoogleMapController { // The StreamController used by this controller and the geometry ones. final StreamController _streamController; + /// The StreamController for the events of this Map. Only for integration testing. + @visibleForTesting + StreamController get stream => _streamController; + /// The Stream over which this controller broadcasts events. Stream get events => _streamController.stream; @@ -132,10 +136,27 @@ class GoogleMapController { return gmaps.GMap(div, options); } - /// Initializes the [gmaps.GMap] instance from the stored `rawOptions`. + /// A flag that returns true if the controller has been initialized or not. + @visibleForTesting + bool get isInitialized => _googleMap != null; + + /// Starts the JS Maps SDK into the target [_div] with `rawOptions`. + /// + /// (Also initializes the geometry/traffic layers.) /// - /// This method actually renders the GMap into the cached `_div`. This is - /// called by the [GoogleMapsPlugin.init] method when appropriate. + /// The first part of this method starts the rendering of a [gmaps.GMap] inside + /// of the target [_div], with configuration from `rawOptions`. It then stores + /// the created GMap in the [_googleMap] attribute. + /// + /// Not *everything* is rendered with the initial `rawOptions` configuration, + /// geometry and traffic layers (and possibly others in the future) have their + /// own configuration and are rendered on top of a GMap instance later. This + /// happens in the second half of this method. + /// + /// This method is eagerly called from the [GoogleMapsPlugin.buildView] method + /// so the internal [GoogleMapsController] of a Web Map initializes as soon as + /// possible. Check [_attachMapEvents] to see how this controller notifies the + /// plugin of it being fully ready (through the `onTilesloaded.first` event). /// /// Failure to call this method would result in the GMap not rendering at all, /// and most of the public methods on this class no-op'ing. @@ -151,6 +172,7 @@ class GoogleMapController { _attachMapEvents(map); _attachGeometryControllers(map); + // Now attach the geometry, traffic and any other layers... _renderInitialGeometry( markers: _markers, circles: _circles, @@ -163,6 +185,10 @@ class GoogleMapController { // Funnels map gmap events into the plugin's stream controller. void _attachMapEvents(gmaps.GMap map) { + map.onTilesloaded.first.then((event) { + // Report the map as ready to go the first time the tiles load + _streamController.add(WebMapReadyEvent(_mapId)); + }); map.onClick.listen((event) { assert(event.latLng != null); _streamController.add( @@ -397,3 +423,9 @@ class GoogleMapController { _streamController.close(); } } + +/// An event fired when a [mapId] on web is interactive. +class WebMapReadyEvent extends MapEvent { + /// Build a WebMapReady Event for the map represented by `mapId`. + WebMapReadyEvent(int mapId) : super(mapId, null); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 692917fef4da..18fda1a06e5c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -35,7 +35,10 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future init(int mapId) async { - _map(mapId).init(); + // The internal instance of our controller is initialized eagerly in `buildView`, + // so we don't have to do anything in this method, which is left intentionally + // blank. + assert(_map(mapId) != null, 'Must call buildWidget before init!'); } /// Updates the options of a given `mapId`. @@ -305,11 +308,15 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { polylines: polylines, circles: circles, mapOptions: mapOptions, - ); + )..init(); // Initialize the controller _mapById[creationId] = mapController; - onPlatformViewCreated.call(creationId); + mapController.events.whereType().first.then((event) { + assert(creationId == event.mapId, 'Received WebMapReadyEvent for the wrong map'); + // *now* we can notify the plugin that there's a fully working controller. + onPlatformViewCreated.call(event.mapId); + }); assert(mapController.widget != null, 'The widget of a GoogleMapController cannot be null before calling dispose on it.'); From 8c58a52e90b29e92aaca5a49e4992de38e2a9876 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:33:00 -0700 Subject: [PATCH 06/17] Add tests for projection methods. --- .../integration_test/projection_test.dart | 138 +++++++++++++----- 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart index 99d974eb4047..16b1dd32d987 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart @@ -8,30 +8,32 @@ // (Tests methods that can't be mocked in `google_maps_controller_test.dart`) import 'dart:async'; -import 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter/google_maps_flutter.dart' show GoogleMap, GoogleMapController; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; -import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' hide GoogleMapController; import 'package:integration_test/integration_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -// This value is used when comparing long~num, like -// LatLng values. -const _acceptableDelta = 0.0000000001; +// This value is used when comparing long~num, like LatLng values. +const _acceptableLatLngDelta = 0.0000000001; + +// This value is used when comparing pixel measurements, mostly to gloss over +// browser rounding errors. +const _acceptablePixelDelta = 1; /// Test Google Map Controller void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('initializes', () { + group('Methods that require a proper Projection', () { final LatLng center = LatLng(43.3078, -5.6958); final Size size = Size(320, 240); + final CameraPosition initialCamera = CameraPosition( + target: center, + zoom: 14, + ); late Completer controllerCompleter; late void Function(GoogleMapController) onMapCreated; @@ -43,52 +45,108 @@ void main() { }; }); - testWidgets('target of map is in center of widget', (WidgetTester tester) async { - await tester.pumpWidget( - CenteredMap( - initialCamera: CameraPosition( - target: center, - zoom: 14, - ), - size: size, - onMapCreated: onMapCreated, - ), - Duration(milliseconds: 500), - ); + group('getScreenCoordinate', () { + testWidgets('target of map is in center of widget', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + + final GoogleMapController controller = await controllerCompleter.future; + + final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(center); + + expect(screenPosition.x, closeTo(size.width / 2, _acceptablePixelDelta)); + expect(screenPosition.y, closeTo(size.height / 2, _acceptablePixelDelta)); + }); + + testWidgets('NorthWest of visible region corresponds to x:0, y:0', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + final GoogleMapController controller = await controllerCompleter.future; + + final LatLngBounds bounds = await controller.getVisibleRegion(); + final LatLng northWest = LatLng(bounds.northeast.latitude, bounds.southwest.longitude); + + final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(northWest); - final GoogleMapController controller = await controllerCompleter.future; + expect(screenPosition.x, closeTo(0, _acceptablePixelDelta)); + expect(screenPosition.y, closeTo(0, _acceptablePixelDelta)); + }); - // This is needed to kick-off the rendering of the Map flutter widget - await tester.pumpAndSettle(Duration(milliseconds: 500)); + testWidgets('SouthEast of visible region corresponds to x:size.width, y:size.height', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + final GoogleMapController controller = await controllerCompleter.future; - // Find the element of the map, and attach a Resize observer to it... - + final LatLngBounds bounds = await controller.getVisibleRegion(); + final LatLng southEast = LatLng(bounds.southwest.latitude, bounds.northeast.longitude); - // This is needed to let the JS map do its thing - await Future.delayed(Duration(milliseconds: 5000)); + final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(southEast); - final ScreenCoordinate coords = await controller.getScreenCoordinate(center); + expect(screenPosition.x, closeTo(size.width, _acceptablePixelDelta)); + expect(screenPosition.y, closeTo(size.height, _acceptablePixelDelta)); + }); + }); + + group('getLatLng', () { + testWidgets('Center of widget is the target of map', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + + final GoogleMapController controller = await controllerCompleter.future; + + final LatLng coords = await controller.getLatLng( + ScreenCoordinate(x: (size.width / 2).round(), y: (size.height / 2).round(),)); + + expect(coords.latitude, closeTo(center.latitude, _acceptableLatLngDelta)); + expect(coords.longitude, closeTo(center.longitude, _acceptableLatLngDelta)); + }); + + testWidgets('Top-left of widget is NorthWest bound of map', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + final GoogleMapController controller = await controllerCompleter.future; + + final LatLngBounds bounds = await controller.getVisibleRegion(); + final LatLng northWest = LatLng(bounds.northeast.latitude, bounds.southwest.longitude); - print(coords); + final LatLng coords = await controller.getLatLng(ScreenCoordinate(x: 0, y: 0,)); - // final ScreenCoordinate coords = (await tester.runAsync(() { - // print('About to wait...'); - // return Future.delayed(Duration(seconds: 10), () { - // print('10 seconds have passed...'); - // return controller.getScreenCoordinate(center); - // }); - // }, additionalTime: Duration(seconds: 10)))!; + expect(coords.latitude, closeTo(northWest.latitude, _acceptableLatLngDelta)); + expect(coords.longitude, closeTo(northWest.longitude, _acceptableLatLngDelta)); + }); - expect(coords.x, size.width / 2); - expect(coords.y, size.height / 2); + testWidgets('Bottom-right of widget is SouthWest bound of map', (WidgetTester tester) async { + pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + final GoogleMapController controller = await controllerCompleter.future; + + final LatLngBounds bounds = await controller.getVisibleRegion(); + final LatLng southEast = LatLng(bounds.southwest.latitude, bounds.northeast.longitude); + + final LatLng coords = await controller.getLatLng(ScreenCoordinate(x: size.width.toInt(), y: size.height.toInt(),)); + + expect(coords.latitude, closeTo(southEast.latitude, _acceptableLatLngDelta)); + expect(coords.longitude, closeTo(southEast.longitude, _acceptableLatLngDelta)); + }); }); }); } +// Pumps a CenteredMap Widget into a given tester, with some parameters +void pumpCenteredMap(WidgetTester tester, { + required CameraPosition initialCamera, + Size size = const Size(320, 240), + void Function(GoogleMapController)? onMapCreated, +}) async { + await tester.pumpWidget( + CenteredMap( + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ), + ); + + // This is needed to kick-off the rendering of the JS Map flutter widget + await tester.pump(); +} + /// Renders a Map widget centered on the screen. /// This depends in `package:google_maps_flutter` to work. class CenteredMap extends StatelessWidget { - const CenteredMap({ required this.initialCamera, required this.size, From 063dc548a81609aabd09be7d7e2345d1201bb2ad Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:33:23 -0700 Subject: [PATCH 07/17] Make google_maps a dev dependency of the example app. --- .../google_maps_flutter_web/example/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 8416c502328f..79b8fbf4d69b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -7,8 +7,6 @@ environment: flutter: ">=2.1.0" dependencies: - google_maps_flutter: # Used for projection_test.dart - path: ../../google_maps_flutter google_maps_flutter_web: path: ../ flutter: @@ -17,6 +15,8 @@ dependencies: dev_dependencies: build_runner: ^2.1.1 google_maps: ^5.1.0 + google_maps_flutter: # Used for projection_test.dart + path: ../../google_maps_flutter http: ^0.13.0 mockito: ^5.0.0 flutter_driver: From b254ab05765f60e4360809e2cce1a90259bc2531 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:54:05 -0700 Subject: [PATCH 08/17] Dartfmt tests --- .../google_maps_plugin_test.dart | 9 +- .../integration_test/projection_test.dart | 173 +++++++++++++----- 2 files changed, 134 insertions(+), 48 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart index 8c55f01f9c2f..758294f5bb91 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart @@ -100,7 +100,7 @@ void main() { expect( cache[testMapId]!.isInitialized, isTrue, - reason: 'buildView calls init on the controller' + reason: 'buildView calls init on the controller', ); }); @@ -137,7 +137,7 @@ void main() { expect( cache[testMapId]!.isInitialized, isTrue, - reason: 'buildView calls init on the controller' + reason: 'buildView calls init on the controller', ); expect( await reportedMapIdCompleter.future, @@ -150,9 +150,11 @@ void main() { expect( numberOnPlatformViewCreatedCalls, equals(1), - reason: 'Should not call onPlatformViewCreated for the same controller multiple times', + reason: + 'Should not call onPlatformViewCreated for the same controller multiple times', ); }); + }); group('setMapStyles', () { String mapStyle = '''[{ @@ -452,7 +454,6 @@ void main() { }); }); }); -}); } class _OtherMapEvent extends MapEvent { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart index 16b1dd32d987..8a5a62013538 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart @@ -12,7 +12,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart' show GoogleMap, GoogleMapController; +import 'package:google_maps_flutter/google_maps_flutter.dart' + show GoogleMap, GoogleMapController; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; @@ -31,9 +32,9 @@ void main() { final LatLng center = LatLng(43.3078, -5.6958); final Size size = Size(320, 240); final CameraPosition initialCamera = CameraPosition( - target: center, - zoom: 14, - ); + target: center, + zoom: 14, + ); late Completer controllerCompleter; late void Function(GoogleMapController) onMapCreated; @@ -46,38 +47,72 @@ void main() { }); group('getScreenCoordinate', () { - testWidgets('target of map is in center of widget', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + testWidgets('target of map is in center of widget', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); final GoogleMapController controller = await controllerCompleter.future; - final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(center); - - expect(screenPosition.x, closeTo(size.width / 2, _acceptablePixelDelta)); - expect(screenPosition.y, closeTo(size.height / 2, _acceptablePixelDelta)); + final ScreenCoordinate screenPosition = + await controller.getScreenCoordinate(center); + + expect( + screenPosition.x, + closeTo(size.width / 2, _acceptablePixelDelta), + ); + expect( + screenPosition.y, + closeTo(size.height / 2, _acceptablePixelDelta), + ); }); - testWidgets('NorthWest of visible region corresponds to x:0, y:0', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + testWidgets('NorthWest of visible region corresponds to x:0, y:0', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); - final LatLng northWest = LatLng(bounds.northeast.latitude, bounds.southwest.longitude); + final LatLng northWest = LatLng( + bounds.northeast.latitude, + bounds.southwest.longitude, + ); - final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(northWest); + final ScreenCoordinate screenPosition = + await controller.getScreenCoordinate(northWest); expect(screenPosition.x, closeTo(0, _acceptablePixelDelta)); expect(screenPosition.y, closeTo(0, _acceptablePixelDelta)); }); - testWidgets('SouthEast of visible region corresponds to x:size.width, y:size.height', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + testWidgets( + 'SouthEast of visible region corresponds to x:size.width, y:size.height', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); - final LatLng southEast = LatLng(bounds.southwest.latitude, bounds.northeast.longitude); + final LatLng southEast = LatLng( + bounds.southwest.latitude, + bounds.northeast.longitude, + ); - final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(southEast); + final ScreenCoordinate screenPosition = + await controller.getScreenCoordinate(southEast); expect(screenPosition.x, closeTo(size.width, _acceptablePixelDelta)); expect(screenPosition.y, closeTo(size.height, _acceptablePixelDelta)); @@ -85,49 +120,97 @@ void main() { }); group('getLatLng', () { - testWidgets('Center of widget is the target of map', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); - - final GoogleMapController controller = await controllerCompleter.future; + testWidgets('Center of widget is the target of map', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); - final LatLng coords = await controller.getLatLng( - ScreenCoordinate(x: (size.width / 2).round(), y: (size.height / 2).round(),)); + final GoogleMapController controller = await controllerCompleter.future; - expect(coords.latitude, closeTo(center.latitude, _acceptableLatLngDelta)); - expect(coords.longitude, closeTo(center.longitude, _acceptableLatLngDelta)); + final LatLng coords = await controller.getLatLng( + ScreenCoordinate(x: size.width ~/ 2, y: size.height ~/ 2), + ); + + expect( + coords.latitude, + closeTo(center.latitude, _acceptableLatLngDelta), + ); + expect( + coords.longitude, + closeTo(center.longitude, _acceptableLatLngDelta), + ); }); - testWidgets('Top-left of widget is NorthWest bound of map', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + testWidgets('Top-left of widget is NorthWest bound of map', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); - final LatLng northWest = LatLng(bounds.northeast.latitude, bounds.southwest.longitude); - - final LatLng coords = await controller.getLatLng(ScreenCoordinate(x: 0, y: 0,)); - - expect(coords.latitude, closeTo(northWest.latitude, _acceptableLatLngDelta)); - expect(coords.longitude, closeTo(northWest.longitude, _acceptableLatLngDelta)); + final LatLng northWest = LatLng( + bounds.northeast.latitude, + bounds.southwest.longitude, + ); + + final LatLng coords = await controller.getLatLng( + ScreenCoordinate(x: 0, y: 0), + ); + + expect( + coords.latitude, + closeTo(northWest.latitude, _acceptableLatLngDelta), + ); + expect( + coords.longitude, + closeTo(northWest.longitude, _acceptableLatLngDelta), + ); }); - testWidgets('Bottom-right of widget is SouthWest bound of map', (WidgetTester tester) async { - pumpCenteredMap(tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated,); + testWidgets('Bottom-right of widget is SouthWest bound of map', + (WidgetTester tester) async { + pumpCenteredMap( + tester, + initialCamera: initialCamera, + size: size, + onMapCreated: onMapCreated, + ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); - final LatLng southEast = LatLng(bounds.southwest.latitude, bounds.northeast.longitude); - - final LatLng coords = await controller.getLatLng(ScreenCoordinate(x: size.width.toInt(), y: size.height.toInt(),)); - - expect(coords.latitude, closeTo(southEast.latitude, _acceptableLatLngDelta)); - expect(coords.longitude, closeTo(southEast.longitude, _acceptableLatLngDelta)); + final LatLng southEast = LatLng( + bounds.southwest.latitude, + bounds.northeast.longitude, + ); + + final LatLng coords = await controller.getLatLng( + ScreenCoordinate(x: size.width.toInt(), y: size.height.toInt()), + ); + + expect( + coords.latitude, + closeTo(southEast.latitude, _acceptableLatLngDelta), + ); + expect( + coords.longitude, + closeTo(southEast.longitude, _acceptableLatLngDelta), + ); }); }); }); } // Pumps a CenteredMap Widget into a given tester, with some parameters -void pumpCenteredMap(WidgetTester tester, { +void pumpCenteredMap( + WidgetTester tester, { required CameraPosition initialCamera, Size size = const Size(320, 240), void Function(GoogleMapController)? onMapCreated, @@ -151,13 +234,15 @@ class CenteredMap extends StatelessWidget { required this.initialCamera, required this.size, required this.onMapCreated, - Key? key + Key? key, }) : super(key: key); /// A function that receives the [GoogleMapController] of the Map widget once initialized. final void Function(GoogleMapController)? onMapCreated; + /// The size of the rendered map widget. final Size size; + /// The initial camera position (center + zoom level) of the Map widget. final CameraPosition initialCamera; From 0bc5a7ff9f68a6d51f0e709a42eb5daf686f2897 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:54:37 -0700 Subject: [PATCH 09/17] dartfmt lib --- .../lib/src/google_maps_flutter_web.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 18fda1a06e5c..b34eebf5e67e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -313,7 +313,8 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { _mapById[creationId] = mapController; mapController.events.whereType().first.then((event) { - assert(creationId == event.mapId, 'Received WebMapReadyEvent for the wrong map'); + assert(creationId == event.mapId, + 'Received WebMapReadyEvent for the wrong map'); // *now* we can notify the plugin that there's a fully working controller. onPlatformViewCreated.call(event.mapId); }); From 1241d6dd056a7e68a80263d8d41936db4dd00ac6 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:56:14 -0700 Subject: [PATCH 10/17] Bump changelog and version --- .../google_maps_flutter_web/CHANGELOG.md | 6 ++++++ .../google_maps_flutter_web/pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 83ffe09b357d..7538d1517f38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.3.1 + +* Fix the `getScreenCoordinate(LatLng)` method. +* Wait until the map tiles have loaded before calling `onPlatformViewCreated`, so +the returned controller is 100% functional (has bounds, a projection, etc...) + ## 0.3.0+4 * Add `implements` to pubspec. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 5910af9ac8db..8a23916b0e98 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.3.0+4 +version: 0.3.1 environment: sdk: ">=2.12.0 <3.0.0" From 2016cd7da261ddecdc6525d06efce8dc3bf30359 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Wed, 1 Sep 2021 17:57:29 -0700 Subject: [PATCH 11/17] Ensure example app uses fixed API too. --- .../google_maps_flutter_web/example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 79b8fbf4d69b..249b893d198c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.1 - google_maps: ^5.1.0 + google_maps: ^5.2.0 google_maps_flutter: # Used for projection_test.dart path: ../../google_maps_flutter http: ^0.13.0 From c8ecb6e358ef59b0987a8d35e0b4855a5c2885dc Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 9 Sep 2021 15:23:13 -0700 Subject: [PATCH 12/17] Move to_screen_location to src/third_party and add proper licensing info. --- .../google_maps_flutter_web/LICENSE | 26 +++++++++ .../lib/google_maps_flutter_web.dart | 1 + .../lib/src/convert.dart | 31 ---------- .../lib/src/google_maps_controller.dart | 2 +- .../third_party/to_screen_location/LICENSE | 5 ++ .../third_party/to_screen_location/README.md | 14 +++++ .../to_screen_location.dart | 57 +++++++++++++++++++ 7 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE create mode 100644 packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/README.md create mode 100644 packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/to_screen_location.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_web/LICENSE b/packages/google_maps_flutter/google_maps_flutter_web/LICENSE index c6823b81eb84..8f8c01d50118 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/LICENSE +++ b/packages/google_maps_flutter/google_maps_flutter_web/LICENSE @@ -1,3 +1,5 @@ +google_maps_flutter_web + Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -23,3 +25,27 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +to_screen_location + +The MIT License (MIT) + +Copyright (c) 2008 Krasimir Tsonev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index 6dc2dab572a6..0355f2923528 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -25,6 +25,7 @@ import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:google_maps/google_maps.dart' as gmaps; +import 'src/third_party/to_screen_location/to_screen_location.dart'; import 'src/types.dart'; part 'src/google_maps_flutter_web.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index b251131b9c0c..2e71c795ff0e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -416,7 +416,6 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { } // original JS by: Byron Singh (https://stackoverflow.com/a/30541162) -// (This is the reverse of [_latLngToPixel]) gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) { final bounds = map.bounds; final projection = map.projection; @@ -442,33 +441,3 @@ gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) { return projection.fromPointToLatLng!(point)!; } - -// original JS by: Krasimir (https://krasimirtsonev.com/blog/article/google-maps-api-v3-convert-latlng-object-to-actual-pixels-point-object) -// (This is the reverse of [_pixelToLatLng]) -gmaps.Point _latLngToPixel(gmaps.GMap map, gmaps.LatLng coords) { - final zoom = map.zoom; - final bounds = map.bounds; - final projection = map.projection; - - assert( - bounds != null, 'Map Bounds required to compute screen x/y of LatLng.'); - assert(projection != null, - 'Map Projection required to compute screen x/y of LatLng.'); - assert(zoom != null, - 'Current map zoom level required to compute screen x/y of LatLng.'); - - final ne = bounds!.northEast; - final sw = bounds.southWest; - - final topRight = projection!.fromLatLngToPoint!(ne)!; - final bottomLeft = projection.fromLatLngToPoint!(sw)!; - - final scale = 1 << (zoom!.toInt()); // 2 ^ zoom - - final worldPoint = projection.fromLatLngToPoint!(coords)!; - - return gmaps.Point( - ((worldPoint.x! - bottomLeft.x!) * scale).toInt(), - ((worldPoint.y! - topRight.y!) * scale).toInt(), - ); -} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index bd14cf13a47e..edf47764f346 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -319,7 +319,7 @@ class GoogleMapController { assert(_googleMap != null, 'Cannot get the screen coordinates with a null map.'); - final point = _latLngToPixel(_googleMap!, _latLngToGmLatLng(latLng)); + final point = toScreenLocation(_googleMap!, _latLngToGmLatLng(latLng)); return ScreenCoordinate(x: point.x!.toInt(), y: point.y!.toInt()); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE new file mode 100644 index 000000000000..69e9f35d84ec --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE @@ -0,0 +1,5 @@ +The MIT License (MIT) + +Copyright (c) 2008 Krasimir Tsonev + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/README.md b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/README.md new file mode 100644 index 000000000000..8bd4a39c065f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/README.md @@ -0,0 +1,14 @@ +# to_screen_location + +The code in this directory is a Dart re-implementation of Krasimir Tsonev's blog +post: [GoogleMaps API v3: convert LatLng object to actual pixels][blog-post]. + +The blog post describes a way to implement the [`toScreenLocation` method][method] +of the Google Maps Platform SDK for the web. + +Used under license (MIT), [available here][blog-license], and in the accompanying +LICENSE file. + +[blog-license]: https://krasimirtsonev.com/license +[blog-post]: https://krasimirtsonev.com/blog/article/google-maps-api-v3-convert-latlng-object-to-actual-pixels-point-object +[method]: https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/Projection#toScreenLocation(com.google.android.libraries.maps.model.LatLng) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/to_screen_location.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/to_screen_location.dart new file mode 100644 index 000000000000..2963111fdcc3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/to_screen_location.dart @@ -0,0 +1,57 @@ +// The MIT License (MIT) +// +// Copyright (c) 2008 Krasimir Tsonev +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import 'package:google_maps/google_maps.dart' as gmaps; + +/// Returns a screen location that corresponds to a geographical coordinate ([gmaps.LatLng]). +/// +/// The screen location is in pixels relative to the top left of the Map widget +/// (not of the whole screen/app). +/// +/// See: https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/Projection#public-point-toscreenlocation-latlng-location +gmaps.Point toScreenLocation(gmaps.GMap map, gmaps.LatLng coords) { + final zoom = map.zoom; + final bounds = map.bounds; + final projection = map.projection; + + assert( + bounds != null, 'Map Bounds required to compute screen x/y of LatLng.'); + assert(projection != null, + 'Map Projection required to compute screen x/y of LatLng.'); + assert(zoom != null, + 'Current map zoom level required to compute screen x/y of LatLng.'); + + final ne = bounds!.northEast; + final sw = bounds.southWest; + + final topRight = projection!.fromLatLngToPoint!(ne)!; + final bottomLeft = projection.fromLatLngToPoint!(sw)!; + + final scale = 1 << (zoom!.toInt()); // 2 ^ zoom + + final worldPoint = projection.fromLatLngToPoint!(coords)!; + + return gmaps.Point( + ((worldPoint.x! - bottomLeft.x!) * scale).toInt(), + ((worldPoint.y! - topRight.y!) * scale).toInt(), + ); +} From 9bbdfa991a57d557e32f6f0768f40862c1cca9d8 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 9 Sep 2021 17:45:47 -0700 Subject: [PATCH 13/17] Use zIndex when creating Circles. --- .../google_maps_controller_test.dart | 19 +++++++++++++------ .../lib/src/convert.dart | 7 ++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart index 1d33eea4c7f3..39aa641b10e4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart @@ -257,13 +257,19 @@ void main() { }); testWidgets('renders initial geometry', (WidgetTester tester) async { - controller = _createController(circles: { - Circle(circleId: CircleId('circle-1')) - }, markers: { + controller = _createController(circles: { + Circle( + circleId: CircleId('circle-1'), + zIndex: 1234, + ), + }, markers: { Marker( - markerId: MarkerId('marker-1'), - infoWindow: InfoWindow( - title: 'title for test', snippet: 'snippet for test')) + markerId: MarkerId('marker-1'), + infoWindow: InfoWindow( + title: 'title for test', + snippet: 'snippet for test', + ), + ), }, polygons: { Polygon(polygonId: PolygonId('polygon-1'), points: [ LatLng(43.355114, -5.851333), @@ -315,6 +321,7 @@ void main() { .captured[0] as Set; expect(capturedCircles.first.circleId.value, 'circle-1'); + expect(capturedCircles.first.zIndex, 1234); expect(capturedMarkers.first.markerId.value, 'marker-1'); expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test'); expect(capturedMarkers.first.infoWindow.title, 'title for test'); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 2e71c795ff0e..c026a03be804 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -264,7 +264,7 @@ gmaps.MarkerOptions _markerOptionsFromMarker( } gmaps.CircleOptions _circleOptionsFromCircle(Circle circle) { - final populationOptions = gmaps.CircleOptions() + final circleOptions = gmaps.CircleOptions() ..strokeColor = _getCssColor(circle.strokeColor) ..strokeOpacity = _getCssOpacity(circle.strokeColor) ..strokeWeight = circle.strokeWidth @@ -272,8 +272,9 @@ gmaps.CircleOptions _circleOptionsFromCircle(Circle circle) { ..fillOpacity = _getCssOpacity(circle.fillColor) ..center = gmaps.LatLng(circle.center.latitude, circle.center.longitude) ..radius = circle.radius - ..visible = circle.visible; - return populationOptions; + ..visible = circle.visible + ..zIndex = circle.zIndex; + return circleOptions; } gmaps.PolygonOptions _polygonOptionsFromPolygon( From 3ca2f71c15036634e33d7f29fea5d1cee75b1f97 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 9 Sep 2021 17:58:00 -0700 Subject: [PATCH 14/17] Update CHANGELOG --- .../google_maps_flutter/google_maps_flutter_web/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 7538d1517f38..4d7ecf74e098 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,8 +1,9 @@ ## 0.3.1 -* Fix the `getScreenCoordinate(LatLng)` method. +* Fix the `getScreenCoordinate(LatLng)` method. [#80710](https://github.com/flutter/flutter/issues/80710) * Wait until the map tiles have loaded before calling `onPlatformViewCreated`, so the returned controller is 100% functional (has bounds, a projection, etc...) +* Use zIndex property when initializing Circle objects. [#89374](https://github.com/flutter/flutter/issues/89374) ## 0.3.0+4 From 544b3664b263ce31851906f7d64eeaecda1f0c0b Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 9 Sep 2021 18:20:54 -0700 Subject: [PATCH 15/17] Update license check command to recognize a new third_party license. --- script/tool/lib/src/license_check_command.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/script/tool/lib/src/license_check_command.dart b/script/tool/lib/src/license_check_command.dart index e68585c44bdf..8c3a9173aecb 100644 --- a/script/tool/lib/src/license_check_command.dart +++ b/script/tool/lib/src/license_check_command.dart @@ -49,12 +49,17 @@ const Set _ignoredFullBasenameList = { // When adding license regexes here, include the copyright info to ensure that // any new additions are flagged for added scrutiny in review. final List _thirdPartyLicenseBlockRegexes = [ -// Third-party code used in url_launcher_web. + // Third-party code used in url_launcher_web. RegExp( r'^// Copyright 2017 Workiva Inc\..*' r'^// Licensed under the Apache License, Version 2\.0', multiLine: true, dotAll: true), + // Third-party code used in google_maps_flutter_web. + RegExp( + r'^// The MIT License [^C]+ Copyright \(c\) 2008 Krasimir Tsonev', + multiLine: true, + ), // bsdiff in flutter/packages. RegExp(r'// Copyright 2003-2005 Colin Percival\. All rights reserved\.\n' r'// Use of this source code is governed by a BSD-style license that can be\n' From c39eabf948d45ce9da56969ce086e6e26d89a393 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Thu, 9 Sep 2021 18:40:12 -0700 Subject: [PATCH 16/17] dart format and some commas --- script/tool/lib/src/license_check_command.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/script/tool/lib/src/license_check_command.dart b/script/tool/lib/src/license_check_command.dart index 8c3a9173aecb..8cee46b45a4c 100644 --- a/script/tool/lib/src/license_check_command.dart +++ b/script/tool/lib/src/license_check_command.dart @@ -51,19 +51,22 @@ const Set _ignoredFullBasenameList = { final List _thirdPartyLicenseBlockRegexes = [ // Third-party code used in url_launcher_web. RegExp( - r'^// Copyright 2017 Workiva Inc\..*' - r'^// Licensed under the Apache License, Version 2\.0', - multiLine: true, - dotAll: true), + r'^// Copyright 2017 Workiva Inc\..*' + r'^// Licensed under the Apache License, Version 2\.0', + multiLine: true, + dotAll: true, + ), // Third-party code used in google_maps_flutter_web. RegExp( r'^// The MIT License [^C]+ Copyright \(c\) 2008 Krasimir Tsonev', multiLine: true, ), // bsdiff in flutter/packages. - RegExp(r'// Copyright 2003-2005 Colin Percival\. All rights reserved\.\n' - r'// Use of this source code is governed by a BSD-style license that can be\n' - r'// found in the LICENSE file\.\n'), + RegExp( + r'// Copyright 2003-2005 Colin Percival\. All rights reserved\.\n' + r'// Use of this source code is governed by a BSD-style license that can be\n' + r'// found in the LICENSE file\.\n', + ), ]; // The exact format of the BSD license that our license files should contain. From a447daa90b7cffa3994680724fb8703df4eb9e49 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Fri, 10 Sep 2021 13:20:44 -0700 Subject: [PATCH 17/17] Do not use we, make LICENSE more legible. --- .../lib/src/google_maps_flutter_web.dart | 2 +- .../src/third_party/to_screen_location/LICENSE | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index b34eebf5e67e..d03dec93ce3f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -315,7 +315,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { mapController.events.whereType().first.then((event) { assert(creationId == event.mapId, 'Received WebMapReadyEvent for the wrong map'); - // *now* we can notify the plugin that there's a fully working controller. + // Notify the plugin now that there's a fully initialized controller. onPlatformViewCreated.call(event.mapId); }); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE index 69e9f35d84ec..ab4e163abe54 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE @@ -2,4 +2,20 @@ The MIT License (MIT) Copyright (c) 2008 Krasimir Tsonev -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.