| // 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. |
| |
| import 'dart:async'; |
| // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) |
| // ignore: unnecessary_import |
| import 'dart:typed_data'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; |
| import 'package:stream_transform/stream_transform.dart'; |
| |
| import 'google_map_inspector_android.dart'; |
| |
| // TODO(stuartmorgan): Remove the dependency on platform interface toJson |
| // methods. Channel serialization details should all be package-internal. |
| |
| /// Error thrown when an unknown map ID is provided to a method channel API. |
| class UnknownMapIDError extends Error { |
| /// Creates an assertion error with the provided [mapId] and optional |
| /// [message]. |
| UnknownMapIDError(this.mapId, [this.message]); |
| |
| /// The unknown ID. |
| final int mapId; |
| |
| /// Message describing the assertion error. |
| final Object? message; |
| |
| @override |
| String toString() { |
| if (message != null) { |
| return 'Unknown map ID $mapId: ${Error.safeToString(message)}'; |
| } |
| return 'Unknown map ID $mapId'; |
| } |
| } |
| |
| /// The possible android map renderer types that can be |
| /// requested from the native Google Maps SDK. |
| enum AndroidMapRenderer { |
| /// Latest renderer type. |
| latest, |
| |
| /// Legacy renderer type. |
| legacy, |
| |
| /// Requests the default map renderer type. |
| platformDefault, |
| } |
| |
| /// An implementation of [GoogleMapsFlutterPlatform] for Android. |
| class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { |
| /// Registers the Android implementation of GoogleMapsFlutterPlatform. |
| static void registerWith() { |
| GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid(); |
| } |
| |
| /// The method channel used to initialize the native Google Maps SDK. |
| final MethodChannel _initializerChannel = const MethodChannel( |
| 'plugins.flutter.dev/google_maps_android_initializer'); |
| |
| // Keep a collection of id -> channel |
| // Every method call passes the int mapId |
| final Map<int, MethodChannel> _channels = <int, MethodChannel>{}; |
| |
| /// Accesses the MethodChannel associated to the passed mapId. |
| MethodChannel _channel(int mapId) { |
| final MethodChannel? channel = _channels[mapId]; |
| if (channel == null) { |
| throw UnknownMapIDError(mapId); |
| } |
| return channel; |
| } |
| |
| // Keep a collection of mapId to a map of TileOverlays. |
| final Map<int, Map<TileOverlayId, TileOverlay>> _tileOverlays = |
| <int, Map<TileOverlayId, TileOverlay>>{}; |
| |
| /// Returns the channel for [mapId], creating it if it doesn't already exist. |
| @visibleForTesting |
| MethodChannel ensureChannelInitialized(int mapId) { |
| MethodChannel? channel = _channels[mapId]; |
| if (channel == null) { |
| channel = MethodChannel('plugins.flutter.dev/google_maps_android_$mapId'); |
| channel.setMethodCallHandler( |
| (MethodCall call) => _handleMethodCall(call, mapId)); |
| _channels[mapId] = channel; |
| } |
| return channel; |
| } |
| |
| @override |
| Future<void> init(int mapId) { |
| final MethodChannel channel = ensureChannelInitialized(mapId); |
| return channel.invokeMethod<void>('map#waitForMap'); |
| } |
| |
| @override |
| void dispose({required int mapId}) { |
| // Noop! |
| } |
| |
| // The controller we need to broadcast the different events coming |
| // from handleMethodCall. |
| // |
| // It is a `broadcast` because multiple controllers will connect to |
| // different stream views of this Controller. |
| final StreamController<MapEvent<Object?>> _mapEventStreamController = |
| StreamController<MapEvent<Object?>>.broadcast(); |
| |
| // Returns a filtered view of the events in the _controller, by mapId. |
| Stream<MapEvent<Object?>> _events(int mapId) => |
| _mapEventStreamController.stream |
| .where((MapEvent<Object?> event) => event.mapId == mapId); |
| |
| @override |
| Stream<CameraMoveStartedEvent> onCameraMoveStarted({required int mapId}) { |
| return _events(mapId).whereType<CameraMoveStartedEvent>(); |
| } |
| |
| @override |
| Stream<CameraMoveEvent> onCameraMove({required int mapId}) { |
| return _events(mapId).whereType<CameraMoveEvent>(); |
| } |
| |
| @override |
| Stream<CameraIdleEvent> onCameraIdle({required int mapId}) { |
| return _events(mapId).whereType<CameraIdleEvent>(); |
| } |
| |
| @override |
| Stream<MarkerTapEvent> onMarkerTap({required int mapId}) { |
| return _events(mapId).whereType<MarkerTapEvent>(); |
| } |
| |
| @override |
| Stream<InfoWindowTapEvent> onInfoWindowTap({required int mapId}) { |
| return _events(mapId).whereType<InfoWindowTapEvent>(); |
| } |
| |
| @override |
| Stream<MarkerDragStartEvent> onMarkerDragStart({required int mapId}) { |
| return _events(mapId).whereType<MarkerDragStartEvent>(); |
| } |
| |
| @override |
| Stream<MarkerDragEvent> onMarkerDrag({required int mapId}) { |
| return _events(mapId).whereType<MarkerDragEvent>(); |
| } |
| |
| @override |
| Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) { |
| return _events(mapId).whereType<MarkerDragEndEvent>(); |
| } |
| |
| @override |
| Stream<PolylineTapEvent> onPolylineTap({required int mapId}) { |
| return _events(mapId).whereType<PolylineTapEvent>(); |
| } |
| |
| @override |
| Stream<PolygonTapEvent> onPolygonTap({required int mapId}) { |
| return _events(mapId).whereType<PolygonTapEvent>(); |
| } |
| |
| @override |
| Stream<CircleTapEvent> onCircleTap({required int mapId}) { |
| return _events(mapId).whereType<CircleTapEvent>(); |
| } |
| |
| @override |
| Stream<MapTapEvent> onTap({required int mapId}) { |
| return _events(mapId).whereType<MapTapEvent>(); |
| } |
| |
| @override |
| Stream<MapLongPressEvent> onLongPress({required int mapId}) { |
| return _events(mapId).whereType<MapLongPressEvent>(); |
| } |
| |
| Future<dynamic> _handleMethodCall(MethodCall call, int mapId) async { |
| switch (call.method) { |
| case 'camera#onMoveStarted': |
| _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); |
| break; |
| case 'camera#onMove': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(CameraMoveEvent( |
| mapId, |
| CameraPosition.fromMap(arguments['position'])!, |
| )); |
| break; |
| case 'camera#onIdle': |
| _mapEventStreamController.add(CameraIdleEvent(mapId)); |
| break; |
| case 'marker#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MarkerTapEvent( |
| mapId, |
| MarkerId(arguments['markerId']! as String), |
| )); |
| break; |
| case 'marker#onDragStart': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MarkerDragStartEvent( |
| mapId, |
| LatLng.fromJson(arguments['position'])!, |
| MarkerId(arguments['markerId']! as String), |
| )); |
| break; |
| case 'marker#onDrag': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MarkerDragEvent( |
| mapId, |
| LatLng.fromJson(arguments['position'])!, |
| MarkerId(arguments['markerId']! as String), |
| )); |
| break; |
| case 'marker#onDragEnd': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MarkerDragEndEvent( |
| mapId, |
| LatLng.fromJson(arguments['position'])!, |
| MarkerId(arguments['markerId']! as String), |
| )); |
| break; |
| case 'infoWindow#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(InfoWindowTapEvent( |
| mapId, |
| MarkerId(arguments['markerId']! as String), |
| )); |
| break; |
| case 'polyline#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(PolylineTapEvent( |
| mapId, |
| PolylineId(arguments['polylineId']! as String), |
| )); |
| break; |
| case 'polygon#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(PolygonTapEvent( |
| mapId, |
| PolygonId(arguments['polygonId']! as String), |
| )); |
| break; |
| case 'circle#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(CircleTapEvent( |
| mapId, |
| CircleId(arguments['circleId']! as String), |
| )); |
| break; |
| case 'map#onTap': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MapTapEvent( |
| mapId, |
| LatLng.fromJson(arguments['position'])!, |
| )); |
| break; |
| case 'map#onLongPress': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| _mapEventStreamController.add(MapLongPressEvent( |
| mapId, |
| LatLng.fromJson(arguments['position'])!, |
| )); |
| break; |
| case 'tileOverlay#getTile': |
| final Map<String, Object?> arguments = _getArgumentDictionary(call); |
| final Map<TileOverlayId, TileOverlay>? tileOverlaysForThisMap = |
| _tileOverlays[mapId]; |
| final String tileOverlayId = arguments['tileOverlayId']! as String; |
| final TileOverlay? tileOverlay = |
| tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; |
| final TileProvider? tileProvider = tileOverlay?.tileProvider; |
| if (tileProvider == null) { |
| return TileProvider.noTile.toJson(); |
| } |
| final Tile tile = await tileProvider.getTile( |
| arguments['x']! as int, |
| arguments['y']! as int, |
| arguments['zoom'] as int?, |
| ); |
| return tile.toJson(); |
| default: |
| throw MissingPluginException(); |
| } |
| } |
| |
| /// Returns the arguments of [call] as typed string-keyed Map. |
| /// |
| /// This does not do any type validation, so is only safe to call if the |
| /// arguments are known to be a map. |
| Map<String, Object?> _getArgumentDictionary(MethodCall call) { |
| return (call.arguments as Map<Object?, Object?>).cast<String, Object?>(); |
| } |
| |
| @override |
| Future<void> updateMapOptions( |
| Map<String, dynamic> optionsUpdate, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'map#update', |
| <String, dynamic>{ |
| 'options': optionsUpdate, |
| }, |
| ); |
| } |
| |
| @override |
| Future<void> updateMarkers( |
| MarkerUpdates markerUpdates, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'markers#update', |
| markerUpdates.toJson(), |
| ); |
| } |
| |
| @override |
| Future<void> updatePolygons( |
| PolygonUpdates polygonUpdates, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'polygons#update', |
| polygonUpdates.toJson(), |
| ); |
| } |
| |
| @override |
| Future<void> updatePolylines( |
| PolylineUpdates polylineUpdates, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'polylines#update', |
| polylineUpdates.toJson(), |
| ); |
| } |
| |
| @override |
| Future<void> updateCircles( |
| CircleUpdates circleUpdates, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'circles#update', |
| circleUpdates.toJson(), |
| ); |
| } |
| |
| @override |
| Future<void> updateTileOverlays({ |
| required Set<TileOverlay> newTileOverlays, |
| required int mapId, |
| }) { |
| final Map<TileOverlayId, TileOverlay>? currentTileOverlays = |
| _tileOverlays[mapId]; |
| final Set<TileOverlay> previousSet = currentTileOverlays != null |
| ? currentTileOverlays.values.toSet() |
| : <TileOverlay>{}; |
| final _TileOverlayUpdates updates = |
| _TileOverlayUpdates.from(previousSet, newTileOverlays); |
| _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays); |
| return _channel(mapId).invokeMethod<void>( |
| 'tileOverlays#update', |
| updates.toJson(), |
| ); |
| } |
| |
| @override |
| Future<void> clearTileCache( |
| TileOverlayId tileOverlayId, { |
| required int mapId, |
| }) { |
| return _channel(mapId) |
| .invokeMethod<void>('tileOverlays#clearTileCache', <String, Object>{ |
| 'tileOverlayId': tileOverlayId.value, |
| }); |
| } |
| |
| @override |
| Future<void> animateCamera( |
| CameraUpdate cameraUpdate, { |
| required int mapId, |
| }) { |
| return _channel(mapId) |
| .invokeMethod<void>('camera#animate', <String, Object>{ |
| 'cameraUpdate': cameraUpdate.toJson(), |
| }); |
| } |
| |
| @override |
| Future<void> moveCamera( |
| CameraUpdate cameraUpdate, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>('camera#move', <String, dynamic>{ |
| 'cameraUpdate': cameraUpdate.toJson(), |
| }); |
| } |
| |
| @override |
| Future<void> setMapStyle( |
| String? mapStyle, { |
| required int mapId, |
| }) async { |
| final List<dynamic> successAndError = (await _channel(mapId) |
| .invokeMethod<List<dynamic>>('map#setStyle', mapStyle))!; |
| final bool success = successAndError[0] as bool; |
| if (!success) { |
| throw MapStyleException(successAndError[1] as String); |
| } |
| } |
| |
| @override |
| Future<LatLngBounds> getVisibleRegion({ |
| required int mapId, |
| }) async { |
| final Map<String, dynamic> latLngBounds = (await _channel(mapId) |
| .invokeMapMethod<String, dynamic>('map#getVisibleRegion'))!; |
| final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; |
| final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; |
| |
| return LatLngBounds(northeast: northeast, southwest: southwest); |
| } |
| |
| @override |
| Future<ScreenCoordinate> getScreenCoordinate( |
| LatLng latLng, { |
| required int mapId, |
| }) async { |
| final Map<String, int> point = (await _channel(mapId) |
| .invokeMapMethod<String, int>( |
| 'map#getScreenCoordinate', latLng.toJson()))!; |
| |
| return ScreenCoordinate(x: point['x']!, y: point['y']!); |
| } |
| |
| @override |
| Future<LatLng> getLatLng( |
| ScreenCoordinate screenCoordinate, { |
| required int mapId, |
| }) async { |
| final List<dynamic> latLng = (await _channel(mapId) |
| .invokeMethod<List<dynamic>>( |
| 'map#getLatLng', screenCoordinate.toJson()))!; |
| return LatLng(latLng[0] as double, latLng[1] as double); |
| } |
| |
| @override |
| Future<void> showMarkerInfoWindow( |
| MarkerId markerId, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'markers#showInfoWindow', <String, String>{'markerId': markerId.value}); |
| } |
| |
| @override |
| Future<void> hideMarkerInfoWindow( |
| MarkerId markerId, { |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<void>( |
| 'markers#hideInfoWindow', <String, String>{'markerId': markerId.value}); |
| } |
| |
| @override |
| Future<bool> isMarkerInfoWindowShown( |
| MarkerId markerId, { |
| required int mapId, |
| }) async { |
| return (await _channel(mapId).invokeMethod<bool>( |
| 'markers#isInfoWindowShown', |
| <String, String>{'markerId': markerId.value}))!; |
| } |
| |
| @override |
| Future<double> getZoomLevel({ |
| required int mapId, |
| }) async { |
| return (await _channel(mapId).invokeMethod<double>('map#getZoomLevel'))!; |
| } |
| |
| @override |
| Future<Uint8List?> takeSnapshot({ |
| required int mapId, |
| }) { |
| return _channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot'); |
| } |
| |
| /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the |
| /// Google Maps widget. |
| /// |
| /// See https://pub.dev/packages/google_maps_flutter_android#display-mode |
| /// for more information. |
| /// |
| /// Currently defaults to true, but the default is subject to change. |
| bool useAndroidViewSurface = true; |
| |
| /// Requests Google Map Renderer with [AndroidMapRenderer] type. |
| /// |
| /// See https://pub.dev/packages/google_maps_flutter_android#map-renderer |
| /// for more information. |
| /// |
| /// The renderer must be requested before creating GoogleMap instances as the |
| /// renderer can be initialized only once per application context. |
| /// Throws a [PlatformException] if method is called multiple times. |
| /// |
| /// The returned [Future] completes after renderer has been initialized. |
| /// Initialized [AndroidMapRenderer] type is returned. |
| Future<AndroidMapRenderer> initializeWithRenderer( |
| AndroidMapRenderer? rendererType) async { |
| String preferredRenderer; |
| switch (rendererType) { |
| case AndroidMapRenderer.latest: |
| preferredRenderer = 'latest'; |
| break; |
| case AndroidMapRenderer.legacy: |
| preferredRenderer = 'legacy'; |
| break; |
| case AndroidMapRenderer.platformDefault: |
| case null: |
| preferredRenderer = 'default'; |
| } |
| |
| final String? initializedRenderer = await _initializerChannel |
| .invokeMethod<String>('initializer#preferRenderer', |
| <String, dynamic>{'value': preferredRenderer}); |
| |
| if (initializedRenderer == null) { |
| throw AndroidMapRendererException('Failed to initialize map renderer.'); |
| } |
| |
| // Returns mapped [AndroidMapRenderer] enum type. |
| switch (initializedRenderer) { |
| case 'latest': |
| return AndroidMapRenderer.latest; |
| case 'legacy': |
| return AndroidMapRenderer.legacy; |
| default: |
| throw AndroidMapRendererException( |
| 'Failed to initialize latest or legacy renderer, got $initializedRenderer.'); |
| } |
| } |
| |
| Widget _buildView( |
| int creationId, |
| PlatformViewCreatedCallback onPlatformViewCreated, { |
| required MapWidgetConfiguration widgetConfiguration, |
| MapObjects mapObjects = const MapObjects(), |
| Map<String, dynamic> mapOptions = const <String, dynamic>{}, |
| }) { |
| final Map<String, dynamic> creationParams = <String, dynamic>{ |
| 'initialCameraPosition': |
| widgetConfiguration.initialCameraPosition.toMap(), |
| 'options': mapOptions, |
| 'markersToAdd': serializeMarkerSet(mapObjects.markers), |
| 'polygonsToAdd': serializePolygonSet(mapObjects.polygons), |
| 'polylinesToAdd': serializePolylineSet(mapObjects.polylines), |
| 'circlesToAdd': serializeCircleSet(mapObjects.circles), |
| 'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays), |
| }; |
| |
| const String viewType = 'plugins.flutter.dev/google_maps_android'; |
| if (useAndroidViewSurface) { |
| return PlatformViewLink( |
| viewType: viewType, |
| surfaceFactory: ( |
| BuildContext context, |
| PlatformViewController controller, |
| ) { |
| return AndroidViewSurface( |
| controller: controller as AndroidViewController, |
| gestureRecognizers: widgetConfiguration.gestureRecognizers, |
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
| ); |
| }, |
| onCreatePlatformView: (PlatformViewCreationParams params) { |
| final AndroidViewController controller = |
| PlatformViewsService.initExpensiveAndroidView( |
| id: params.id, |
| viewType: viewType, |
| layoutDirection: widgetConfiguration.textDirection, |
| creationParams: creationParams, |
| creationParamsCodec: const StandardMessageCodec(), |
| onFocus: () => params.onFocusChanged(true), |
| ); |
| controller.addOnPlatformViewCreatedListener( |
| params.onPlatformViewCreated, |
| ); |
| controller.addOnPlatformViewCreatedListener( |
| onPlatformViewCreated, |
| ); |
| |
| controller.create(); |
| return controller; |
| }, |
| ); |
| } else { |
| return AndroidView( |
| viewType: viewType, |
| onPlatformViewCreated: onPlatformViewCreated, |
| gestureRecognizers: widgetConfiguration.gestureRecognizers, |
| creationParams: creationParams, |
| creationParamsCodec: const StandardMessageCodec(), |
| ); |
| } |
| } |
| |
| @override |
| Widget buildViewWithConfiguration( |
| int creationId, |
| PlatformViewCreatedCallback onPlatformViewCreated, { |
| required MapWidgetConfiguration widgetConfiguration, |
| MapConfiguration mapConfiguration = const MapConfiguration(), |
| MapObjects mapObjects = const MapObjects(), |
| }) { |
| return _buildView( |
| creationId, |
| onPlatformViewCreated, |
| widgetConfiguration: widgetConfiguration, |
| mapObjects: mapObjects, |
| mapOptions: _jsonForMapConfiguration(mapConfiguration), |
| ); |
| } |
| |
| @override |
| Widget buildViewWithTextDirection( |
| int creationId, |
| PlatformViewCreatedCallback onPlatformViewCreated, { |
| required CameraPosition initialCameraPosition, |
| required TextDirection textDirection, |
| Set<Marker> markers = const <Marker>{}, |
| Set<Polygon> polygons = const <Polygon>{}, |
| Set<Polyline> polylines = const <Polyline>{}, |
| Set<Circle> circles = const <Circle>{}, |
| Set<TileOverlay> tileOverlays = const <TileOverlay>{}, |
| Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
| Map<String, dynamic> mapOptions = const <String, dynamic>{}, |
| }) { |
| return _buildView( |
| creationId, |
| onPlatformViewCreated, |
| widgetConfiguration: MapWidgetConfiguration( |
| initialCameraPosition: initialCameraPosition, |
| textDirection: textDirection), |
| mapObjects: MapObjects( |
| markers: markers, |
| polygons: polygons, |
| polylines: polylines, |
| circles: circles, |
| tileOverlays: tileOverlays), |
| mapOptions: mapOptions, |
| ); |
| } |
| |
| @override |
| Widget buildView( |
| int creationId, |
| PlatformViewCreatedCallback onPlatformViewCreated, { |
| required CameraPosition initialCameraPosition, |
| Set<Marker> markers = const <Marker>{}, |
| Set<Polygon> polygons = const <Polygon>{}, |
| Set<Polyline> polylines = const <Polyline>{}, |
| Set<Circle> circles = const <Circle>{}, |
| Set<TileOverlay> tileOverlays = const <TileOverlay>{}, |
| Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
| Map<String, dynamic> mapOptions = const <String, dynamic>{}, |
| }) { |
| return buildViewWithTextDirection( |
| creationId, |
| onPlatformViewCreated, |
| initialCameraPosition: initialCameraPosition, |
| textDirection: TextDirection.ltr, |
| markers: markers, |
| polygons: polygons, |
| polylines: polylines, |
| circles: circles, |
| tileOverlays: tileOverlays, |
| gestureRecognizers: gestureRecognizers, |
| mapOptions: mapOptions, |
| ); |
| } |
| |
| @override |
| @visibleForTesting |
| void enableDebugInspection() { |
| GoogleMapsInspectorPlatform.instance = |
| GoogleMapsInspectorAndroid((int mapId) => _channel(mapId)); |
| } |
| } |
| |
| Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) { |
| final EdgeInsets? padding = config.padding; |
| return <String, Object>{ |
| if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!, |
| if (config.mapToolbarEnabled != null) |
| 'mapToolbarEnabled': config.mapToolbarEnabled!, |
| if (config.cameraTargetBounds != null) |
| 'cameraTargetBounds': config.cameraTargetBounds!.toJson(), |
| if (config.mapType != null) 'mapType': config.mapType!.index, |
| if (config.minMaxZoomPreference != null) |
| 'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(), |
| if (config.rotateGesturesEnabled != null) |
| 'rotateGesturesEnabled': config.rotateGesturesEnabled!, |
| if (config.scrollGesturesEnabled != null) |
| 'scrollGesturesEnabled': config.scrollGesturesEnabled!, |
| if (config.tiltGesturesEnabled != null) |
| 'tiltGesturesEnabled': config.tiltGesturesEnabled!, |
| if (config.zoomControlsEnabled != null) |
| 'zoomControlsEnabled': config.zoomControlsEnabled!, |
| if (config.zoomGesturesEnabled != null) |
| 'zoomGesturesEnabled': config.zoomGesturesEnabled!, |
| if (config.liteModeEnabled != null) |
| 'liteModeEnabled': config.liteModeEnabled!, |
| if (config.trackCameraPosition != null) |
| 'trackCameraPosition': config.trackCameraPosition!, |
| if (config.myLocationEnabled != null) |
| 'myLocationEnabled': config.myLocationEnabled!, |
| if (config.myLocationButtonEnabled != null) |
| 'myLocationButtonEnabled': config.myLocationButtonEnabled!, |
| if (padding != null) |
| 'padding': <double>[ |
| padding.top, |
| padding.left, |
| padding.bottom, |
| padding.right, |
| ], |
| if (config.indoorViewEnabled != null) |
| 'indoorEnabled': config.indoorViewEnabled!, |
| if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!, |
| if (config.buildingsEnabled != null) |
| 'buildingsEnabled': config.buildingsEnabled!, |
| }; |
| } |
| |
| /// Update specification for a set of [TileOverlay]s. |
| // TODO(stuartmorgan): Fix the missing export of this class in the platform |
| // interface, and remove this copy. |
| class _TileOverlayUpdates extends MapsObjectUpdates<TileOverlay> { |
| /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s. |
| _TileOverlayUpdates.from(super.previous, super.current) |
| : super.from(objectName: 'tileOverlay'); |
| |
| /// Set of TileOverlays to be added in this update. |
| Set<TileOverlay> get tileOverlaysToAdd => objectsToAdd; |
| |
| /// Set of TileOverlayIds to be removed in this update. |
| Set<TileOverlayId> get tileOverlayIdsToRemove => |
| objectIdsToRemove.cast<TileOverlayId>(); |
| |
| /// Set of TileOverlays to be changed in this update. |
| Set<TileOverlay> get tileOverlaysToChange => objectsToChange; |
| } |
| |
| /// Thrown to indicate that a platform interaction failed to initialize renderer. |
| class AndroidMapRendererException implements Exception { |
| /// Creates a [AndroidMapRendererException] with an optional human-readable |
| /// error message. |
| AndroidMapRendererException([this.message]); |
| |
| /// A human-readable error message, possibly null. |
| final String? message; |
| |
| @override |
| String toString() => 'AndroidMapRendererException($message)'; |
| } |