blob: e2c713163a2a527093a0be203d033e1dcead3af2 [file] [log] [blame]
// 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
// This is a pared down version of the Dart code from the app-facing package,
// to allow running the same examples for package-local testing.
// TODO(stuartmorgan): Consider extracting this to a shared package. See also
// https://github.com/flutter/flutter/issues/46716.
/// Controller for a single ExampleGoogleMap instance running on the host platform.
class ExampleGoogleMapController {
ExampleGoogleMapController._(
this._googleMapState, {
required this.mapId,
}) {
_connectStreams(mapId);
}
/// The mapId for this controller
final int mapId;
/// Initialize control of a [ExampleGoogleMap] with [id].
///
/// Mainly for internal use when instantiating a [ExampleGoogleMapController] passed
/// in [ExampleGoogleMap.onMapCreated] callback.
static Future<ExampleGoogleMapController> _init(
int id,
CameraPosition initialCameraPosition,
_ExampleGoogleMapState googleMapState,
) async {
await GoogleMapsFlutterPlatform.instance.init(id);
return ExampleGoogleMapController._(
googleMapState,
mapId: id,
);
}
final _ExampleGoogleMapState _googleMapState;
void _connectStreams(int mapId) {
if (_googleMapState.widget.onCameraMoveStarted != null) {
GoogleMapsFlutterPlatform.instance
.onCameraMoveStarted(mapId: mapId)
.listen((_) => _googleMapState.widget.onCameraMoveStarted!());
}
if (_googleMapState.widget.onCameraMove != null) {
GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen(
(CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value));
}
if (_googleMapState.widget.onCameraIdle != null) {
GoogleMapsFlutterPlatform.instance
.onCameraIdle(mapId: mapId)
.listen((_) => _googleMapState.widget.onCameraIdle!());
}
GoogleMapsFlutterPlatform.instance
.onMarkerTap(mapId: mapId)
.listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value));
GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen(
(MarkerDragStartEvent e) =>
_googleMapState.onMarkerDragStart(e.value, e.position));
GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen(
(MarkerDragEvent e) =>
_googleMapState.onMarkerDrag(e.value, e.position));
GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen(
(MarkerDragEndEvent e) =>
_googleMapState.onMarkerDragEnd(e.value, e.position));
GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen(
(InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value));
GoogleMapsFlutterPlatform.instance
.onPolylineTap(mapId: mapId)
.listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value));
GoogleMapsFlutterPlatform.instance
.onPolygonTap(mapId: mapId)
.listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value));
GoogleMapsFlutterPlatform.instance
.onCircleTap(mapId: mapId)
.listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value));
GoogleMapsFlutterPlatform.instance
.onTap(mapId: mapId)
.listen((MapTapEvent e) => _googleMapState.onTap(e.position));
GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen(
(MapLongPressEvent e) => _googleMapState.onLongPress(e.position));
}
/// Updates configuration options of the map user interface.
Future<void> _updateMapConfiguration(MapConfiguration update) {
return GoogleMapsFlutterPlatform.instance
.updateMapConfiguration(update, mapId: mapId);
}
/// Updates marker configuration.
Future<void> _updateMarkers(MarkerUpdates markerUpdates) {
return GoogleMapsFlutterPlatform.instance
.updateMarkers(markerUpdates, mapId: mapId);
}
/// Updates polygon configuration.
Future<void> _updatePolygons(PolygonUpdates polygonUpdates) {
return GoogleMapsFlutterPlatform.instance
.updatePolygons(polygonUpdates, mapId: mapId);
}
/// Updates polyline configuration.
Future<void> _updatePolylines(PolylineUpdates polylineUpdates) {
return GoogleMapsFlutterPlatform.instance
.updatePolylines(polylineUpdates, mapId: mapId);
}
/// Updates circle configuration.
Future<void> _updateCircles(CircleUpdates circleUpdates) {
return GoogleMapsFlutterPlatform.instance
.updateCircles(circleUpdates, mapId: mapId);
}
/// Updates tile overlays configuration.
Future<void> _updateTileOverlays(Set<TileOverlay> newTileOverlays) {
return GoogleMapsFlutterPlatform.instance
.updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId);
}
/// Clears the tile cache so that all tiles will be requested again from the
/// [TileProvider].
Future<void> clearTileCache(TileOverlayId tileOverlayId) async {
return GoogleMapsFlutterPlatform.instance
.clearTileCache(tileOverlayId, mapId: mapId);
}
/// Starts an animated change of the map camera position.
Future<void> animateCamera(CameraUpdate cameraUpdate) {
return GoogleMapsFlutterPlatform.instance
.animateCamera(cameraUpdate, mapId: mapId);
}
/// Changes the map camera position.
Future<void> moveCamera(CameraUpdate cameraUpdate) {
return GoogleMapsFlutterPlatform.instance
.moveCamera(cameraUpdate, mapId: mapId);
}
/// Sets the styling of the base map.
Future<void> setMapStyle(String? mapStyle) {
return GoogleMapsFlutterPlatform.instance
.setMapStyle(mapStyle, mapId: mapId);
}
/// Return [LatLngBounds] defining the region that is visible in a map.
Future<LatLngBounds> getVisibleRegion() {
return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId);
}
/// Return [ScreenCoordinate] of the [LatLng] in the current map view.
Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) {
return GoogleMapsFlutterPlatform.instance
.getScreenCoordinate(latLng, mapId: mapId);
}
/// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view.
Future<LatLng> getLatLng(ScreenCoordinate screenCoordinate) {
return GoogleMapsFlutterPlatform.instance
.getLatLng(screenCoordinate, mapId: mapId);
}
/// Programmatically show the Info Window for a [Marker].
Future<void> showMarkerInfoWindow(MarkerId markerId) {
return GoogleMapsFlutterPlatform.instance
.showMarkerInfoWindow(markerId, mapId: mapId);
}
/// Programmatically hide the Info Window for a [Marker].
Future<void> hideMarkerInfoWindow(MarkerId markerId) {
return GoogleMapsFlutterPlatform.instance
.hideMarkerInfoWindow(markerId, mapId: mapId);
}
/// Returns `true` when the [InfoWindow] is showing, `false` otherwise.
Future<bool> isMarkerInfoWindowShown(MarkerId markerId) {
return GoogleMapsFlutterPlatform.instance
.isMarkerInfoWindowShown(markerId, mapId: mapId);
}
/// Returns the current zoom level of the map
Future<double> getZoomLevel() {
return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId);
}
/// Returns the image bytes of the map
Future<Uint8List?> takeSnapshot() {
return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId);
}
/// Disposes of the platform resources
void dispose() {
GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId);
}
}
// The next map ID to create.
int _nextMapCreationId = 0;
/// A widget which displays a map with data obtained from the Google Maps service.
class ExampleGoogleMap extends StatefulWidget {
/// Creates a widget displaying data from Google Maps services.
///
/// [AssertionError] will be thrown if [initialCameraPosition] is null;
const ExampleGoogleMap({
Key? key,
required this.initialCameraPosition,
this.onMapCreated,
this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{},
this.compassEnabled = true,
this.mapToolbarEnabled = true,
this.cameraTargetBounds = CameraTargetBounds.unbounded,
this.mapType = MapType.normal,
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
this.rotateGesturesEnabled = true,
this.scrollGesturesEnabled = true,
this.zoomControlsEnabled = true,
this.zoomGesturesEnabled = true,
this.liteModeEnabled = false,
this.tiltGesturesEnabled = true,
this.myLocationEnabled = false,
this.myLocationButtonEnabled = true,
this.layoutDirection,
/// If no padding is specified default padding will be 0.
this.padding = const EdgeInsets.all(0),
this.indoorViewEnabled = false,
this.trafficEnabled = false,
this.buildingsEnabled = true,
this.markers = const <Marker>{},
this.polygons = const <Polygon>{},
this.polylines = const <Polyline>{},
this.circles = const <Circle>{},
this.onCameraMoveStarted,
this.tileOverlays = const <TileOverlay>{},
this.onCameraMove,
this.onCameraIdle,
this.onTap,
this.onLongPress,
}) : super(key: key);
/// Callback method for when the map is ready to be used.
///
/// Used to receive a [ExampleGoogleMapController] for this [ExampleGoogleMap].
final void Function(ExampleGoogleMapController controller)? onMapCreated;
/// The initial position of the map's camera.
final CameraPosition initialCameraPosition;
/// True if the map should show a compass when rotated.
final bool compassEnabled;
/// True if the map should show a toolbar when you interact with the map. Android only.
final bool mapToolbarEnabled;
/// Geographical bounding box for the camera target.
final CameraTargetBounds cameraTargetBounds;
/// Type of map tiles to be rendered.
final MapType mapType;
/// The layout direction to use for the embedded view.
final TextDirection? layoutDirection;
/// Preferred bounds for the camera zoom level.
///
/// Actual bounds depend on map data and device.
final MinMaxZoomPreference minMaxZoomPreference;
/// True if the map view should respond to rotate gestures.
final bool rotateGesturesEnabled;
/// True if the map view should respond to scroll gestures.
final bool scrollGesturesEnabled;
/// True if the map view should show zoom controls. This includes two buttons
/// to zoom in and zoom out. The default value is to show zoom controls.
final bool zoomControlsEnabled;
/// True if the map view should respond to zoom gestures.
final bool zoomGesturesEnabled;
/// True if the map view should be in lite mode. Android only.
final bool liteModeEnabled;
/// True if the map view should respond to tilt gestures.
final bool tiltGesturesEnabled;
/// Padding to be set on map.
final EdgeInsets padding;
/// Markers to be placed on the map.
final Set<Marker> markers;
/// Polygons to be placed on the map.
final Set<Polygon> polygons;
/// Polylines to be placed on the map.
final Set<Polyline> polylines;
/// Circles to be placed on the map.
final Set<Circle> circles;
/// Tile overlays to be placed on the map.
final Set<TileOverlay> tileOverlays;
/// Called when the camera starts moving.
final VoidCallback? onCameraMoveStarted;
/// Called repeatedly as the camera continues to move after an
/// onCameraMoveStarted call.
final CameraPositionCallback? onCameraMove;
/// Called when camera movement has ended, there are no pending
/// animations and the user has stopped interacting with the map.
final VoidCallback? onCameraIdle;
/// Called every time a [ExampleGoogleMap] is tapped.
final ArgumentCallback<LatLng>? onTap;
/// Called every time a [ExampleGoogleMap] is long pressed.
final ArgumentCallback<LatLng>? onLongPress;
/// True if a "My Location" layer should be shown on the map.
final bool myLocationEnabled;
/// Enables or disables the my-location button.
final bool myLocationButtonEnabled;
/// Enables or disables the indoor view from the map
final bool indoorViewEnabled;
/// Enables or disables the traffic layer of the map
final bool trafficEnabled;
/// Enables or disables showing 3D buildings where available
final bool buildingsEnabled;
/// Which gestures should be consumed by the map.
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
/// Creates a [State] for this [ExampleGoogleMap].
@override
State createState() => _ExampleGoogleMapState();
}
class _ExampleGoogleMapState extends State<ExampleGoogleMap> {
final int _mapId = _nextMapCreationId++;
final Completer<ExampleGoogleMapController> _controller =
Completer<ExampleGoogleMapController>();
Map<MarkerId, Marker> _markers = <MarkerId, Marker>{};
Map<PolygonId, Polygon> _polygons = <PolygonId, Polygon>{};
Map<PolylineId, Polyline> _polylines = <PolylineId, Polyline>{};
Map<CircleId, Circle> _circles = <CircleId, Circle>{};
late MapConfiguration _mapConfiguration;
@override
Widget build(BuildContext context) {
return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration(
_mapId,
onPlatformViewCreated,
widgetConfiguration: MapWidgetConfiguration(
textDirection: widget.layoutDirection ??
Directionality.maybeOf(context) ??
TextDirection.ltr,
initialCameraPosition: widget.initialCameraPosition,
gestureRecognizers: widget.gestureRecognizers,
),
mapObjects: MapObjects(
markers: widget.markers,
polygons: widget.polygons,
polylines: widget.polylines,
circles: widget.circles,
),
mapConfiguration: _mapConfiguration,
);
}
@override
void initState() {
super.initState();
_mapConfiguration = _configurationFromMapWidget(widget);
_markers = keyByMarkerId(widget.markers);
_polygons = keyByPolygonId(widget.polygons);
_polylines = keyByPolylineId(widget.polylines);
_circles = keyByCircleId(widget.circles);
}
@override
void dispose() {
_controller.future
.then((ExampleGoogleMapController controller) => controller.dispose());
super.dispose();
}
@override
void didUpdateWidget(ExampleGoogleMap oldWidget) {
super.didUpdateWidget(oldWidget);
_updateOptions();
_updateMarkers();
_updatePolygons();
_updatePolylines();
_updateCircles();
_updateTileOverlays();
}
Future<void> _updateOptions() async {
final MapConfiguration newConfig = _configurationFromMapWidget(widget);
final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration);
if (updates.isEmpty) {
return;
}
final ExampleGoogleMapController controller = await _controller.future;
controller._updateMapConfiguration(updates);
_mapConfiguration = newConfig;
}
Future<void> _updateMarkers() async {
final ExampleGoogleMapController controller = await _controller.future;
controller._updateMarkers(
MarkerUpdates.from(_markers.values.toSet(), widget.markers));
_markers = keyByMarkerId(widget.markers);
}
Future<void> _updatePolygons() async {
final ExampleGoogleMapController controller = await _controller.future;
controller._updatePolygons(
PolygonUpdates.from(_polygons.values.toSet(), widget.polygons));
_polygons = keyByPolygonId(widget.polygons);
}
Future<void> _updatePolylines() async {
final ExampleGoogleMapController controller = await _controller.future;
controller._updatePolylines(
PolylineUpdates.from(_polylines.values.toSet(), widget.polylines));
_polylines = keyByPolylineId(widget.polylines);
}
Future<void> _updateCircles() async {
final ExampleGoogleMapController controller = await _controller.future;
controller._updateCircles(
CircleUpdates.from(_circles.values.toSet(), widget.circles));
_circles = keyByCircleId(widget.circles);
}
Future<void> _updateTileOverlays() async {
final ExampleGoogleMapController controller = await _controller.future;
controller._updateTileOverlays(widget.tileOverlays);
}
Future<void> onPlatformViewCreated(int id) async {
final ExampleGoogleMapController controller =
await ExampleGoogleMapController._init(
id,
widget.initialCameraPosition,
this,
);
_controller.complete(controller);
_updateTileOverlays();
widget.onMapCreated?.call(controller);
}
void onMarkerTap(MarkerId markerId) {
_markers[markerId]!.onTap?.call();
}
void onMarkerDragStart(MarkerId markerId, LatLng position) {
_markers[markerId]!.onDragStart?.call(position);
}
void onMarkerDrag(MarkerId markerId, LatLng position) {
_markers[markerId]!.onDrag?.call(position);
}
void onMarkerDragEnd(MarkerId markerId, LatLng position) {
_markers[markerId]!.onDragEnd?.call(position);
}
void onPolygonTap(PolygonId polygonId) {
_polygons[polygonId]!.onTap?.call();
}
void onPolylineTap(PolylineId polylineId) {
_polylines[polylineId]!.onTap?.call();
}
void onCircleTap(CircleId circleId) {
_circles[circleId]!.onTap?.call();
}
void onInfoWindowTap(MarkerId markerId) {
_markers[markerId]!.infoWindow.onTap?.call();
}
void onTap(LatLng position) {
widget.onTap?.call(position);
}
void onLongPress(LatLng position) {
widget.onLongPress?.call(position);
}
}
/// Builds a [MapConfiguration] from the given [map].
MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
return MapConfiguration(
compassEnabled: map.compassEnabled,
mapToolbarEnabled: map.mapToolbarEnabled,
cameraTargetBounds: map.cameraTargetBounds,
mapType: map.mapType,
minMaxZoomPreference: map.minMaxZoomPreference,
rotateGesturesEnabled: map.rotateGesturesEnabled,
scrollGesturesEnabled: map.scrollGesturesEnabled,
tiltGesturesEnabled: map.tiltGesturesEnabled,
trackCameraPosition: map.onCameraMove != null,
zoomControlsEnabled: map.zoomControlsEnabled,
zoomGesturesEnabled: map.zoomGesturesEnabled,
liteModeEnabled: map.liteModeEnabled,
myLocationEnabled: map.myLocationEnabled,
myLocationButtonEnabled: map.myLocationButtonEnabled,
padding: map.padding,
indoorViewEnabled: map.indoorViewEnabled,
trafficEnabled: map.trafficEnabled,
buildingsEnabled: map.buildingsEnabled,
);
}