Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[google_maps_flutter] Add structure options to platform interface #5960

Prev Previous commit
Next Next commit
Update web
stuartmorgan committed May 24, 2022
commit 388b35eceebcb7a4c67ca573d9daebd9e773589e
Original file line number Diff line number Diff line change
@@ -40,21 +40,17 @@ void main() {
GoogleMapController _createController({
CameraPosition initialCameraPosition =
const CameraPosition(target: LatLng(0, 0)),
Set<Marker> markers = const <Marker>{},
Set<Polygon> polygons = const <Polygon>{},
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
Map<String, dynamic> options = const <String, dynamic>{},
MapObjects mapObjects = const MapObjects(),
MapConfiguration mapConfiguration = const MapConfiguration(),
}) {
return GoogleMapController(
mapId: mapId,
streamController: stream,
initialCameraPosition: initialCameraPosition,
markers: markers,
polygons: polygons,
polylines: polylines,
circles: circles,
mapOptions: options,
widgetConfiguration: MapWidgetConfiguration(
initialCameraPosition: initialCameraPosition,
textDirection: TextDirection.ltr),
mapObjects: mapObjects,
mapConfiguration: mapConfiguration,
);
}

@@ -284,7 +280,8 @@ void main() {
});

testWidgets('renders initial geometry', (WidgetTester tester) async {
controller = _createController(circles: <Circle>{
controller = _createController(
mapObjects: MapObjects(circles: <Circle>{
const Circle(
circleId: CircleId('circle-1'),
zIndex: 1234,
@@ -327,7 +324,7 @@ void main() {
LatLng(43.354469, -5.851318),
LatLng(43.354762, -5.850824),
])
});
}));

controller.debugSetOverrides(
circles: circles,
@@ -363,9 +360,10 @@ void main() {

testWidgets('empty infoWindow does not create InfoWindow instance.',
(WidgetTester tester) async {
controller = _createController(markers: <Marker>{
controller = _createController(
mapObjects: MapObjects(markers: <Marker>{
const Marker(markerId: MarkerId('marker-1')),
});
}));

controller.debugSetOverrides(
markers: markers,
@@ -385,10 +383,11 @@ void main() {
capturedOptions = null;
});
testWidgets('translates initial options', (WidgetTester tester) async {
controller = _createController(options: <String, dynamic>{
'mapType': 2,
'zoomControlsEnabled': true,
});
controller = _createController(
mapConfiguration: const MapConfiguration(
mapType: MapType.satellite,
zoomControlsEnabled: true,
));
controller.debugSetOverrides(
createMap: (_, gmaps.MapOptions options) {
capturedOptions = options;
@@ -407,9 +406,10 @@ void main() {

testWidgets('disables gestureHandling with scrollGesturesEnabled false',
(WidgetTester tester) async {
controller = _createController(options: <String, dynamic>{
'scrollGesturesEnabled': false,
});
controller = _createController(
mapConfiguration: const MapConfiguration(
scrollGesturesEnabled: false,
));
controller.debugSetOverrides(
createMap: (_, gmaps.MapOptions options) {
capturedOptions = options;
@@ -426,9 +426,10 @@ void main() {

testWidgets('disables gestureHandling with zoomGesturesEnabled false',
(WidgetTester tester) async {
controller = _createController(options: <String, dynamic>{
'zoomGesturesEnabled': false,
});
controller = _createController(
mapConfiguration: const MapConfiguration(
zoomGesturesEnabled: false,
));
controller.debugSetOverrides(
createMap: (_, gmaps.MapOptions options) {
capturedOptions = options;
@@ -477,9 +478,10 @@ void main() {

testWidgets('initializes with traffic layer',
(WidgetTester tester) async {
controller = _createController(options: <String, dynamic>{
'trafficEnabled': true,
});
controller = _createController(
mapConfiguration: const MapConfiguration(
trafficEnabled: true,
));
controller.debugSetOverrides(createMap: (_, __) => map);
controller.init();
expect(controller.trafficLayer, isNotNull);
@@ -505,25 +507,25 @@ void main() {

group('updateRawOptions', () {
testWidgets('can update `options`', (WidgetTester tester) async {
controller.updateRawOptions(<String, dynamic>{
'mapType': 2,
});
controller.updateMapConfiguration(const MapConfiguration(
mapType: MapType.satellite,
));

expect(map.mapTypeId, gmaps.MapTypeId.SATELLITE);
});

testWidgets('can turn on/off traffic', (WidgetTester tester) async {
expect(controller.trafficLayer, isNull);

controller.updateRawOptions(<String, dynamic>{
'trafficEnabled': true,
});
controller.updateMapConfiguration(const MapConfiguration(
trafficEnabled: true,
));

expect(controller.trafficLayer, isNotNull);

controller.updateRawOptions(<String, dynamic>{
'trafficEnabled': false,
});
controller.updateMapConfiguration(const MapConfiguration(
trafficEnabled: false,
));

expect(controller.trafficLayer, isNull);
});
Original file line number Diff line number Diff line change
@@ -21,7 +21,13 @@ dev_dependencies:
google_maps: ^6.1.0
google_maps_flutter: # Used for projection_test.dart
path: ../../google_maps_flutter
google_maps_flutter_platform_interface: ^2.1.2
http: ^0.13.0
integration_test:
sdk: flutter
mockito: ^5.0.0

# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
google_maps_flutter_platform_interface:
path: ../../../google_maps_flutter/google_maps_flutter_platform_interface
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@ import 'dart:html';
import 'dart:js_util';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
Original file line number Diff line number Diff line change
@@ -13,16 +13,6 @@ final gmaps.LatLngBounds _nullGmapsLatLngBounds =
const String _defaultCssColor = '#000000';
const double _defaultCssOpacity = 0.0;

// Indices in the plugin side don't match with the ones
// in the gmaps lib. This translates from plugin -> gmaps.
final Map<int, gmaps.MapTypeId> _mapTypeToMapTypeId = <int, gmaps.MapTypeId>{
0: gmaps.MapTypeId.ROADMAP, // "none" in the plugin
1: gmaps.MapTypeId.ROADMAP,
2: gmaps.MapTypeId.SATELLITE,
3: gmaps.MapTypeId.TERRAIN,
4: gmaps.MapTypeId.HYBRID,
};

// Converts a [Color] into a valid CSS value #RRGGBB.
String _getCssColor(Color color) {
if (color == null) {
@@ -55,49 +45,64 @@ double _getCssOpacity(Color color) {
// indoorViewEnabled seems to not have an equivalent in web
// buildingsEnabled seems to not have an equivalent in web
// padding seems to behave differently in web than mobile. You can't move UI elements in web.
gmaps.MapOptions _rawOptionsToGmapsOptions(Map<String, Object?> rawOptions) {
gmaps.MapOptions _configurationAndStyleToGmapsOptions(
MapConfiguration configuration, List<gmaps.MapTypeStyle> styles) {
final gmaps.MapOptions options = gmaps.MapOptions();

if (_mapTypeToMapTypeId.containsKey(rawOptions['mapType'])) {
options.mapTypeId = _mapTypeToMapTypeId[rawOptions['mapType']];
if (configuration.mapType != null) {
options.mapTypeId = _gmapTypeIDForPluginType(configuration.mapType!);
}

if (rawOptions['minMaxZoomPreference'] != null) {
final List<Object?> minMaxPreference =
rawOptions['minMaxZoomPreference']! as List<Object?>;
final MinMaxZoomPreference? zoomPreference =
configuration.minMaxZoomPreference;
if (zoomPreference != null) {
options
..minZoom = minMaxPreference[0] as num?
..maxZoom = minMaxPreference[1] as num?;
..minZoom = zoomPreference.minZoom
..maxZoom = zoomPreference.maxZoom;
}

if (rawOptions['cameraTargetBounds'] != null) {
if (configuration.cameraTargetBounds != null) {
// Needs gmaps.MapOptions.restriction and gmaps.MapRestriction
// see: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.restriction
}

if (rawOptions['zoomControlsEnabled'] != null) {
options.zoomControl = rawOptions['zoomControlsEnabled'] as bool?;
}

if (rawOptions['styles'] != null) {
options.styles = rawOptions['styles'] as List<gmaps.MapTypeStyle?>?;
if (configuration.zoomControlsEnabled != null) {
options.zoomControl = configuration.zoomControlsEnabled;
}

if (rawOptions['scrollGesturesEnabled'] == false ||
rawOptions['zoomGesturesEnabled'] == false) {
if (configuration.scrollGesturesEnabled == false ||
configuration.zoomGesturesEnabled == false) {
options.gestureHandling = 'none';
} else {
options.gestureHandling = 'auto';
}

// These don't have any rawOptions entry, but they seem to be off in the native maps.
// These don't have any configuration entries, but they seem to be off in the
// native maps.
options.mapTypeControl = false;
options.fullscreenControl = false;
options.streetViewControl = false;

options.styles = styles;

return options;
}

gmaps.MapTypeId _gmapTypeIDForPluginType(MapType type) {
switch (type) {
case MapType.satellite:
return gmaps.MapTypeId.SATELLITE;
case MapType.terrain:
return gmaps.MapTypeId.TERRAIN;
case MapType.hybrid:
return gmaps.MapTypeId.HYBRID;
case MapType.normal:
case MapType.none:
default:
return gmaps.MapTypeId.ROADMAP;
}
}

gmaps.MapOptions _applyInitialPosition(
CameraPosition initialPosition,
gmaps.MapOptions options,
@@ -111,11 +116,6 @@ gmaps.MapOptions _applyInitialPosition(
return options;
}

// Extracts the status of the traffic layer from the rawOptions map.
bool _isTrafficLayerEnabled(Map<String, Object?> rawOptions) {
return rawOptions['trafficEnabled'] as bool? ?? false;
}

// The keys we'd expect to see in a serialized MapTypeStyle JSON object.
final Set<String> _mapStyleKeys = <String>{
'elementType',
Original file line number Diff line number Diff line change
@@ -15,20 +15,17 @@ class GoogleMapController {
GoogleMapController({
required int mapId,
required StreamController<MapEvent<Object?>> streamController,
required CameraPosition initialCameraPosition,
Set<Marker> markers = const <Marker>{},
Set<Polygon> polygons = const <Polygon>{},
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
Map<String, dynamic> mapOptions = const <String, dynamic>{},
required MapWidgetConfiguration widgetConfiguration,
MapObjects mapObjects = const MapObjects(),
MapConfiguration mapConfiguration = const MapConfiguration(),
}) : _mapId = mapId,
_streamController = streamController,
_initialCameraPosition = initialCameraPosition,
_markers = markers,
_polygons = polygons,
_polylines = polylines,
_circles = circles,
_rawMapOptions = mapOptions {
_initialCameraPosition = widgetConfiguration.initialCameraPosition,
_markers = mapObjects.markers,
_polygons = mapObjects.polygons,
_polylines = mapObjects.polylines,
_circles = mapObjects.circles,
_lastMapConfiguration = mapConfiguration {
_circlesController = CirclesController(stream: _streamController);
_polygonsController = PolygonsController(stream: _streamController);
_polylinesController = PolylinesController(stream: _streamController);
@@ -56,9 +53,10 @@ class GoogleMapController {
final Set<Polygon> _polygons;
final Set<Polyline> _polylines;
final Set<Circle> _circles;
// The raw options passed by the user, before converting to gmaps.
// The configuraiton passed by the user, before converting to gmaps.
// Caching this allows us to re-create the map faithfully when needed.
Map<String, dynamic> _rawMapOptions = <String, dynamic>{};
MapConfiguration _lastMapConfiguration = const MapConfiguration();
List<gmaps.MapTypeStyle> _lastStyles = const <gmaps.MapTypeStyle>[];

// Creates the 'viewType' for the _widget
String _getViewType(int mapId) => 'plugins.flutter.io/google_maps_$mapId';
@@ -158,7 +156,8 @@ class GoogleMapController {
/// 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.
void init() {
gmaps.MapOptions options = _rawOptionsToGmapsOptions(_rawMapOptions);
gmaps.MapOptions options = _configurationAndStyleToGmapsOptions(
_lastMapConfiguration, _lastStyles);
// Initial position can only to be set here!
options = _applyInitialPosition(_initialCameraPosition, options);

@@ -177,7 +176,7 @@ class GoogleMapController {
polylines: _polylines,
);

_setTrafficLayer(map, _isTrafficLayerEnabled(_rawMapOptions));
_setTrafficLayer(map, _lastMapConfiguration.trafficEnabled ?? false);
}

// Funnels map gmap events into the plugin's stream controller.
@@ -260,27 +259,33 @@ class GoogleMapController {
_polylinesController!.addPolylines(polylines);
}

// Merges new options coming from the plugin into the _rawMapOptions map.
// Merges new options coming from the plugin into _lastConfiguration.
//
// Returns the updated _rawMapOptions object.
Map<String, dynamic> _mergeRawOptions(Map<String, dynamic> newOptions) {
_rawMapOptions = <String, dynamic>{
..._rawMapOptions,
...newOptions,
};
return _rawMapOptions;
// Returns the updated _lastConfiguration object.
MapConfiguration _mergeConfigurations(MapConfiguration update) {
_lastMapConfiguration = _lastMapConfiguration.applyDiff(update);
return _lastMapConfiguration;
}

/// Updates the map options from a `Map<String, dynamic>`.
/// Updates the map options from a [MapConfiguration].
///
/// This method converts the map into the proper [gmaps.MapOptions]
void updateRawOptions(Map<String, dynamic> optionsUpdate) {
/// This method converts the map into the proper [gmaps.MapOptions].
void updateMapConfiguration(MapConfiguration update) {
assert(_googleMap != null, 'Cannot update options on a null map.');

final Map<String, dynamic> newOptions = _mergeRawOptions(optionsUpdate);
final MapConfiguration newConfiguration = _mergeConfigurations(update);
final gmaps.MapOptions newOptions =
_configurationAndStyleToGmapsOptions(newConfiguration, _lastStyles);

_setOptions(newOptions);
_setTrafficLayer(_googleMap!, newConfiguration.trafficEnabled ?? false);
}

_setOptions(_rawOptionsToGmapsOptions(newOptions));
_setTrafficLayer(_googleMap!, _isTrafficLayerEnabled(newOptions));
/// Updates the map options with a new list of [styles].
void updateStyles(List<gmaps.MapTypeStyle> styles) {
_lastStyles = styles;
_setOptions(
_configurationAndStyleToGmapsOptions(_lastMapConfiguration, styles));
}

// Sets new [gmaps.MapOptions] on the wrapped map.
Loading