From 514b7a1c0ca9b1bb769aed94b9c73b99a2e6a9a4 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Mon, 20 Apr 2020 13:51:09 -0700 Subject: [PATCH] [google_maps_flutter] Introduce google_maps_flutter_platform_interface. (#2637) This package contains the platform interface (and the MethodChannel default implementation) for the Google Maps Flutter plugin, up to version 0.5.26. The most drastic change is including Streams of MapEvents for all different types of events that may come from the native side. Core types of the plugin (LatLng, Marker, Circle...) now live in this package. Co-authored-by: chung2012 --- .../CHANGELOG.md | 3 + .../LICENSE | 27 + .../README.md | 26 + ...oogle_maps_flutter_platform_interface.dart | 7 + .../lib/src/events/map_event.dart | 153 ++++++ .../method_channel_google_maps_flutter.dart | 477 ++++++++++++++++++ .../google_maps_flutter_platform.dart | 314 ++++++++++++ .../lib/src/types/bitmap.dart | 116 +++++ .../lib/src/types/callbacks.dart | 63 +++ .../lib/src/types/camera.dart | 197 ++++++++ .../lib/src/types/cap.dart | 55 ++ .../lib/src/types/circle.dart | 167 ++++++ .../lib/src/types/circle_updates.dart | 110 ++++ .../lib/src/types/joint_type.dart | 29 ++ .../lib/src/types/location.dart | 129 +++++ .../lib/src/types/marker.dart | 324 ++++++++++++ .../lib/src/types/marker_updates.dart | 110 ++++ .../lib/src/types/pattern_item.dart | 35 ++ .../lib/src/types/polygon.dart | 186 +++++++ .../lib/src/types/polygon_updates.dart | 110 ++++ .../lib/src/types/polyline.dart | 247 +++++++++ .../lib/src/types/polyline_updates.dart | 111 ++++ .../lib/src/types/screen_coordinate.dart | 46 ++ .../lib/src/types/types.dart | 28 + .../lib/src/types/ui.dart | 118 +++++ .../lib/src/types/utils/circle.dart | 22 + .../lib/src/types/utils/marker.dart | 22 + .../lib/src/types/utils/polygon.dart | 22 + .../lib/src/types/utils/polyline.dart | 25 + .../pubspec.yaml | 23 + .../google_maps_flutter_platform_test.dart | 71 +++ script/build_all_plugins_app.sh | 2 + 32 files changed, 3375 insertions(+) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/LICENSE create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/README.md create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/callbacks.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/camera.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/joint_type.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/pattern_item.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/screen_coordinate.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ui.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/circle.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/marker.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polygon.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polyline.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..0d8803f93540 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Initial release. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/LICENSE b/packages/google_maps_flutter/google_maps_flutter_platform_interface/LICENSE new file mode 100644 index 000000000000..8940a4be1b58 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 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. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/README.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/README.md new file mode 100644 index 000000000000..6489ba39cbd8 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/README.md @@ -0,0 +1,26 @@ +# google_maps_flutter_platform_interface + +A common platform interface for the [`google_maps_flutter`][1] plugin. + +This interface allows platform-specific implementations of the `google_maps_flutter` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `google_maps_flutter`, extend +[`GoogleMapsFlutterPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`GoogleMapsFlutterPlatform` by calling +`GoogleMapsFlutterPlatform.instance = MyPlatformGoogleMapsFlutter()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../google_maps_flutter +[2]: lib/google_maps_flutter_platform_interface.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart new file mode 100644 index 000000000000..cb28b40470fd --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart @@ -0,0 +1,7 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/platform_interface/google_maps_flutter_platform.dart'; +export 'src/types/types.dart'; +export 'src/events/map_event.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart new file mode 100644 index 000000000000..c462b4b182e2 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart @@ -0,0 +1,153 @@ +// Copyright 2018 The Chromium 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 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart'; + +/// Generic Event coming from the native side of Maps. +/// +/// All MapEvents contain the `mapId` that originated the event. This should +/// never be `null`. +/// +/// The `` on this event represents the type of the `value` that is +/// contained within the event. +/// +/// This class is used as a base class for all the events that might be +/// triggered from a Map, but it is never used directly as an event type. +/// +/// Do NOT instantiate new events like `MapEvent(mapId, val)` directly, +/// use a specific class instead: +/// +/// Do `class NewEvent extend MapEvent` when creating your own events. +/// See below for examples: `CameraMoveStartedEvent`, `MarkerDragEndEvent`... +/// These events are more semantic and pleasant to use than raw generics. They +/// can be (and in fact, are) filtered by the `instanceof`-operator. +/// +/// (See [MethodChannelGoogleMapsFlutter.onCameraMoveStarted], for example) +/// +/// If your event needs a `position`, alongside the `value`, do +/// `extends _PositionedMapEvent` instead. This adds a `LatLng position` +/// attribute. +/// +/// If your event *only* needs a `position`, do `extend _PositionedMapEvent` +/// do NOT `extend MapEvent`. The former lets consumers of these +/// events to access the `.position` property, rather than the more generic `.value` +/// yielded from the latter. +class MapEvent { + /// The ID of the Map this event is associated to. + final int mapId; + + /// The value wrapped by this event + final T value; + + /// Build a Map Event, that relates a mapId with a given value. + /// + /// The `mapId` is the id of the map that triggered the event. + /// `value` may be `null` in events that don't transport any meaningful data. + MapEvent(this.mapId, this.value); +} + +/// A `MapEvent` associated to a `position`. +class _PositionedMapEvent extends MapEvent { + /// The position where this event happened. + final LatLng position; + + /// Build a Positioned MapEvent, that relates a mapId and a position with a value. + /// + /// The `mapId` is the id of the map that triggered the event. + /// `value` may be `null` in events that don't transport any meaningful data. + _PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); +} + +// The following events are the ones exposed to the end user. They are semantic extensions +// of the two base classes above. +// +// These events are used to create the appropriate [Stream] objects, with information +// coming from the native side. + +/// An event fired when the Camera of a [mapId] starts moving. +class CameraMoveStartedEvent extends MapEvent { + /// Build a CameraMoveStarted Event triggered from the map represented by `mapId`. + CameraMoveStartedEvent(int mapId) : super(mapId, null); +} + +/// An event fired while the Camera of a [mapId] moves. +class CameraMoveEvent extends MapEvent { + /// Build a CameraMove Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [CameraPosition] object with the current position of the Camera. + CameraMoveEvent(int mapId, CameraPosition position) : super(mapId, position); +} + +/// An event fired when the Camera of a [mapId] becomes idle. +class CameraIdleEvent extends MapEvent { + /// Build a CameraIdle Event triggered from the map represented by `mapId`. + CameraIdleEvent(int mapId) : super(mapId, null); +} + +/// An event fired when a [Marker] is tapped. +class MarkerTapEvent extends MapEvent { + /// Build a MarkerTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [MarkerId] object that represents the tapped Marker. + MarkerTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId); +} + +/// An event fired when an [InfoWindow] is tapped. +class InfoWindowTapEvent extends MapEvent { + /// Build an InfoWindowTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [MarkerId] object that represents the tapped InfoWindow. + InfoWindowTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId); +} + +/// An event fired when a [Marker] is dragged to a new [LatLng]. +class MarkerDragEndEvent extends _PositionedMapEvent { + /// Build a MarkerDragEnd Event triggered from the map represented by `mapId`. + /// + /// The `position` on this event is the [LatLng] on which the Marker was dropped. + /// The `value` of this event is a [MarkerId] object that represents the moved Marker. + MarkerDragEndEvent(int mapId, LatLng position, MarkerId markerId) + : super(mapId, position, markerId); +} + +/// An event fired when a [Polyline] is tapped. +class PolylineTapEvent extends MapEvent { + /// Build an PolylineTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [PolylineId] object that represents the tapped Polyline. + PolylineTapEvent(int mapId, PolylineId polylineId) : super(mapId, polylineId); +} + +/// An event fired when a [Polygon] is tapped. +class PolygonTapEvent extends MapEvent { + /// Build an PolygonTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [PolygonId] object that represents the tapped Polygon. + PolygonTapEvent(int mapId, PolygonId polygonId) : super(mapId, polygonId); +} + +/// An event fired when a [Circle] is tapped. +class CircleTapEvent extends MapEvent { + /// Build an CircleTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [CircleId] object that represents the tapped Circle. + CircleTapEvent(int mapId, CircleId circleId) : super(mapId, circleId); +} + +/// An event fired when a Map is tapped. +class MapTapEvent extends _PositionedMapEvent { + /// Build an MapTap Event triggered from the map represented by `mapId`. + /// + /// The `position` of this event is the LatLng where the Map was tapped. + MapTapEvent(int mapId, LatLng position) : super(mapId, position, null); +} + +/// An event fired when a Map is long pressed. +class MapLongPressEvent extends _PositionedMapEvent { + /// Build an MapTap Event triggered from the map represented by `mapId`. + /// + /// The `position` of this event is the LatLng where the Map was long pressed. + MapLongPressEvent(int mapId, LatLng position) : super(mapId, position, null); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart new file mode 100644 index 000000000000..edbc51ab5afd --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -0,0 +1,477 @@ +// Copyright 2017 The Chromium 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'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart'; + +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:stream_transform/stream_transform.dart'; + +/// An implementation of [GoogleMapsFlutterPlatform] that uses [MethodChannel] to communicate with the native code. +/// +/// The `google_maps_flutter` plugin code itself never talks to the native code directly. It delegates +/// all those calls to an instance of a class that extends the GoogleMapsFlutterPlatform. +/// +/// The architecture above allows for platforms that communicate differently with the native side +/// (like web) to have a common interface to extend. +/// +/// This is the instance that runs when the native side talks to your Flutter app through MethodChannels, +/// like the Android and iOS platforms. +class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { + // Keep a collection of id -> channel + // Every method call passes the int mapId + final Map _channels = {}; + + /// Accesses the MethodChannel associated to the passed mapId. + MethodChannel channel(int mapId) { + return _channels[mapId]; + } + + /// Initializes the platform interface with [id]. + /// + /// This method is called when the plugin is first initialized. + @override + Future init(int mapId) { + MethodChannel channel; + if (!_channels.containsKey(mapId)) { + channel = MethodChannel('plugins.flutter.io/google_maps_$mapId'); + channel.setMethodCallHandler( + (MethodCall call) => _handleMethodCall(call, mapId)); + _channels[mapId] = channel; + } + return channel.invokeMethod('map#waitForMap'); + } + + // 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 _mapEventStreamController = + StreamController.broadcast(); + + // Returns a filtered view of the events in the _controller, by mapId. + Stream _events(int mapId) => + _mapEventStreamController.stream.where((event) => event.mapId == mapId); + + @override + Stream onCameraMoveStarted({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onCameraMove({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onCameraIdle({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onMarkerTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onInfoWindowTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onMarkerDragEnd({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onPolylineTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onPolygonTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onCircleTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onTap({@required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onLongPress({@required int mapId}) { + return _events(mapId).whereType(); + } + + Future _handleMethodCall(MethodCall call, int mapId) async { + switch (call.method) { + case 'camera#onMoveStarted': + _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); + break; + case 'camera#onMove': + _mapEventStreamController.add(CameraMoveEvent( + mapId, + CameraPosition.fromMap(call.arguments['position']), + )); + break; + case 'camera#onIdle': + _mapEventStreamController.add(CameraIdleEvent(mapId)); + break; + case 'marker#onTap': + _mapEventStreamController.add(MarkerTapEvent( + mapId, + MarkerId(call.arguments['markerId']), + )); + break; + case 'marker#onDragEnd': + _mapEventStreamController.add(MarkerDragEndEvent( + mapId, + LatLng.fromJson(call.arguments['position']), + MarkerId(call.arguments['markerId']), + )); + break; + case 'infoWindow#onTap': + _mapEventStreamController.add(InfoWindowTapEvent( + mapId, + MarkerId(call.arguments['markerId']), + )); + break; + case 'polyline#onTap': + _mapEventStreamController.add(PolylineTapEvent( + mapId, + PolylineId(call.arguments['polylineId']), + )); + break; + case 'polygon#onTap': + _mapEventStreamController.add(PolygonTapEvent( + mapId, + PolygonId(call.arguments['polygonId']), + )); + break; + case 'circle#onTap': + _mapEventStreamController.add(CircleTapEvent( + mapId, + CircleId(call.arguments['circleId']), + )); + break; + case 'map#onTap': + _mapEventStreamController.add(MapTapEvent( + mapId, + LatLng.fromJson(call.arguments['position']), + )); + break; + case 'map#onLongPress': + _mapEventStreamController.add(MapLongPressEvent( + mapId, + LatLng.fromJson(call.arguments['position']), + )); + break; + default: + throw MissingPluginException(); + } + } + + /// Updates configuration options of the map user interface. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updateMapOptions( + Map optionsUpdate, { + @required int mapId, + }) { + assert(optionsUpdate != null); + return channel(mapId).invokeMethod( + 'map#update', + { + 'options': optionsUpdate, + }, + ); + } + + /// Updates marker configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updateMarkers( + MarkerUpdates markerUpdates, { + @required int mapId, + }) { + assert(markerUpdates != null); + return channel(mapId).invokeMethod( + 'markers#update', + markerUpdates.toJson(), + ); + } + + /// Updates polygon configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updatePolygons( + PolygonUpdates polygonUpdates, { + @required int mapId, + }) { + assert(polygonUpdates != null); + return channel(mapId).invokeMethod( + 'polygons#update', + polygonUpdates.toJson(), + ); + } + + /// Updates polyline configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updatePolylines( + PolylineUpdates polylineUpdates, { + @required int mapId, + }) { + assert(polylineUpdates != null); + return channel(mapId).invokeMethod( + 'polylines#update', + polylineUpdates.toJson(), + ); + } + + /// Updates circle configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updateCircles( + CircleUpdates circleUpdates, { + @required int mapId, + }) { + assert(circleUpdates != null); + return channel(mapId).invokeMethod( + 'circles#update', + circleUpdates.toJson(), + ); + } + + /// Starts an animated change of the map camera position. + /// + /// The returned [Future] completes after the change has been started on the + /// platform side. + @override + Future animateCamera( + CameraUpdate cameraUpdate, { + @required int mapId, + }) { + return channel(mapId) + .invokeMethod('camera#animate', { + 'cameraUpdate': cameraUpdate.toJson(), + }); + } + + /// Changes the map camera position. + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + @override + Future moveCamera( + CameraUpdate cameraUpdate, { + @required int mapId, + }) { + return channel(mapId).invokeMethod('camera#move', { + 'cameraUpdate': cameraUpdate.toJson(), + }); + } + + /// Sets the styling of the base map. + /// + /// Set to `null` to clear any previous custom styling. + /// + /// If problems were detected with the [mapStyle], including un-parsable + /// styling JSON, unrecognized feature type, unrecognized element type, or + /// invalid styler keys: [MapStyleException] is thrown and the current + /// style is left unchanged. + /// + /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). + /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) + /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) + /// style reference for more information regarding the supported styles. + @override + Future setMapStyle( + String mapStyle, { + @required int mapId, + }) async { + final List successAndError = await channel(mapId) + .invokeMethod>('map#setStyle', mapStyle); + final bool success = successAndError[0]; + if (!success) { + throw MapStyleException(successAndError[1]); + } + } + + /// Return the region that is visible in a map. + @override + Future getVisibleRegion({ + @required int mapId, + }) async { + final Map latLngBounds = await channel(mapId) + .invokeMapMethod('map#getVisibleRegion'); + final LatLng southwest = LatLng.fromJson(latLngBounds['southwest']); + final LatLng northeast = LatLng.fromJson(latLngBounds['northeast']); + + return LatLngBounds(northeast: northeast, southwest: southwest); + } + + /// Return point [Map] of the [screenCoordinateInJson] in the current map view. + /// + /// A projection is used to translate between on screen location and geographic coordinates. + /// Screen location is in screen pixels (not display pixels) with respect to the top left corner + /// of the map, not necessarily of the whole screen. + @override + Future getScreenCoordinate( + LatLng latLng, { + @required int mapId, + }) async { + final Map point = await channel(mapId) + .invokeMapMethod( + 'map#getScreenCoordinate', latLng.toJson()); + + return ScreenCoordinate(x: point['x'], y: point['y']); + } + + /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. + /// + /// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen + /// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen. + @override + Future getLatLng( + ScreenCoordinate screenCoordinate, { + @required int mapId, + }) async { + final List latLng = await channel(mapId) + .invokeMethod>( + 'map#getLatLng', screenCoordinate.toJson()); + return LatLng(latLng[0], latLng[1]); + } + + /// Programmatically show the Info Window for a [Marker]. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [hideMarkerInfoWindow] to hide the Info Window. + /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. + @override + Future showMarkerInfoWindow( + MarkerId markerId, { + @required int mapId, + }) { + assert(markerId != null); + return channel(mapId).invokeMethod( + 'markers#showInfoWindow', {'markerId': markerId.value}); + } + + /// Programmatically hide the Info Window for a [Marker]. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [showMarkerInfoWindow] to show the Info Window. + /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. + @override + Future hideMarkerInfoWindow( + MarkerId markerId, { + @required int mapId, + }) { + assert(markerId != null); + return channel(mapId).invokeMethod( + 'markers#hideInfoWindow', {'markerId': markerId.value}); + } + + /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [showMarkerInfoWindow] to show the Info Window. + /// * [hideMarkerInfoWindow] to hide the Info Window. + @override + Future isMarkerInfoWindowShown( + MarkerId markerId, { + @required int mapId, + }) { + assert(markerId != null); + return channel(mapId).invokeMethod('markers#isInfoWindowShown', + {'markerId': markerId.value}); + } + + /// Returns the current zoom level of the map + @override + Future getZoomLevel({ + @required int mapId, + }) { + return channel(mapId).invokeMethod('map#getZoomLevel'); + } + + /// Returns the image bytes of the map + @override + Future takeSnapshot({ + @required int mapId, + }) { + return channel(mapId).invokeMethod('map#takeSnapshot'); + } + + /// This method builds the appropriate platform view where the map + /// can be rendered. + /// The `mapId` is passed as a parameter from the framework on the + /// `onPlatformViewCreated` callback. + @override + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + PlatformViewCreatedCallback onPlatformViewCreated) { + if (defaultTargetPlatform == TargetPlatform.android) { + return AndroidView( + viewType: 'plugins.flutter.io/google_maps', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: 'plugins.flutter.io/google_maps', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return Text( + '$defaultTargetPlatform is not yet supported by the maps plugin'); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart new file mode 100644 index 000000000000..b89d3420c68e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart @@ -0,0 +1,314 @@ +// Copyright 2017 The Chromium 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'; +import 'dart:typed_data'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; + +import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// The interface that platform-specific implementations of `google_maps_flutter` must extend. +/// +/// Avoid `implements` of this interface. Using `implements` makes adding any new +/// methods here a breaking change for end users of your platform! +/// +/// Do `extends GoogleMapsFlutterPlatform` instead, so new methods added here are +/// inherited in your code with the default implementation (that throws at runtime), +/// rather than breaking your users at compile time. +abstract class GoogleMapsFlutterPlatform extends PlatformInterface { + /// Constructs a GoogleMapsFlutterPlatform. + GoogleMapsFlutterPlatform() : super(token: _token); + + static final Object _token = Object(); + + static GoogleMapsFlutterPlatform _instance = MethodChannelGoogleMapsFlutter(); + + /// The default instance of [GoogleMapsFlutterPlatform] to use. + /// + /// Defaults to [MethodChannelGoogleMapsFlutter]. + static GoogleMapsFlutterPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [GoogleMapsFlutterPlatform] when they register themselves. + static set instance(GoogleMapsFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// /// Initializes the platform interface with [id]. + /// + /// This method is called when the plugin is first initialized. + Future init(int mapId) { + throw UnimplementedError('init() has not been implemented.'); + } + + /// Updates configuration options of the map user interface. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updateMapOptions( + Map optionsUpdate, { + @required int mapId, + }) { + throw UnimplementedError('updateMapOptions() has not been implemented.'); + } + + /// Updates marker configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updateMarkers( + MarkerUpdates markerUpdates, { + @required int mapId, + }) { + throw UnimplementedError('updateMarkers() has not been implemented.'); + } + + /// Updates polygon configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updatePolygons( + PolygonUpdates polygonUpdates, { + @required int mapId, + }) { + throw UnimplementedError('updatePolygons() has not been implemented.'); + } + + /// Updates polyline configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updatePolylines( + PolylineUpdates polylineUpdates, { + @required int mapId, + }) { + throw UnimplementedError('updatePolylines() has not been implemented.'); + } + + /// Updates circle configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updateCircles( + CircleUpdates circleUpdates, { + @required int mapId, + }) { + throw UnimplementedError('updateCircles() has not been implemented.'); + } + + /// Starts an animated change of the map camera position. + /// + /// The returned [Future] completes after the change has been started on the + /// platform side. + Future animateCamera( + CameraUpdate cameraUpdate, { + @required int mapId, + }) { + throw UnimplementedError('animateCamera() has not been implemented.'); + } + + /// Changes the map camera position. + /// + /// The returned [Future] completes after the change has been made on the + /// platform side. + Future moveCamera( + CameraUpdate cameraUpdate, { + @required int mapId, + }) { + throw UnimplementedError('moveCamera() has not been implemented.'); + } + + /// Sets the styling of the base map. + /// + /// Set to `null` to clear any previous custom styling. + /// + /// If problems were detected with the [mapStyle], including un-parsable + /// styling JSON, unrecognized feature type, unrecognized element type, or + /// invalid styler keys: [MapStyleException] is thrown and the current + /// style is left unchanged. + /// + /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). + Future setMapStyle( + String mapStyle, { + @required int mapId, + }) { + throw UnimplementedError('setMapStyle() has not been implemented.'); + } + + /// Return the region that is visible in a map. + Future getVisibleRegion({ + @required int mapId, + }) { + throw UnimplementedError('getVisibleRegion() has not been implemented.'); + } + + /// Return [ScreenCoordinate] of the [LatLng] in the current map view. + /// + /// A projection is used to translate between on screen location and geographic coordinates. + /// Screen location is in screen pixels (not display pixels) with respect to the top left corner + /// of the map, not necessarily of the whole screen. + Future getScreenCoordinate( + LatLng latLng, { + @required int mapId, + }) { + throw UnimplementedError('getScreenCoordinate() has not been implemented.'); + } + + /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. + /// + /// A projection is used to translate between on screen location and geographic coordinates. + /// Screen location is in screen pixels (not display pixels) with respect to the top left corner + /// of the map, not necessarily of the whole screen. + Future getLatLng( + ScreenCoordinate screenCoordinate, { + @required int mapId, + }) { + throw UnimplementedError('getLatLng() has not been implemented.'); + } + + /// Programmatically show the Info Window for a [Marker]. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [hideMarkerInfoWindow] to hide the Info Window. + /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. + Future showMarkerInfoWindow( + MarkerId markerId, { + @required int mapId, + }) { + throw UnimplementedError( + 'showMarkerInfoWindow() has not been implemented.'); + } + + /// Programmatically hide the Info Window for a [Marker]. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [showMarkerInfoWindow] to show the Info Window. + /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. + Future hideMarkerInfoWindow( + MarkerId markerId, { + @required int mapId, + }) { + throw UnimplementedError( + 'hideMarkerInfoWindow() has not been implemented.'); + } + + /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. + /// + /// The `markerId` must match one of the markers on the map. + /// An invalid `markerId` triggers an "Invalid markerId" error. + /// + /// * See also: + /// * [showMarkerInfoWindow] to show the Info Window. + /// * [hideMarkerInfoWindow] to hide the Info Window. + Future isMarkerInfoWindowShown( + MarkerId markerId, { + @required int mapId, + }) { + throw UnimplementedError('updateMapOptions() has not been implemented.'); + } + + /// Returns the current zoom level of the map + Future getZoomLevel({ + @required int mapId, + }) { + throw UnimplementedError('getZoomLevel() has not been implemented.'); + } + + /// Returns the image bytes of the map + Future takeSnapshot({ + @required int mapId, + }) { + throw UnimplementedError('takeSnapshot() has not been implemented.'); + } + + // The following are the 11 possible streams of data from the native side + // into the plugin + + /// The Camera started moving. + Stream onCameraMoveStarted({@required int mapId}) { + throw UnimplementedError('onCameraMoveStarted() has not been implemented.'); + } + + /// The Camera finished moving to a new [CameraPosition]. + Stream onCameraMove({@required int mapId}) { + throw UnimplementedError('onCameraMove() has not been implemented.'); + } + + /// The Camera is now idle. + Stream onCameraIdle({@required int mapId}) { + throw UnimplementedError('onCameraMove() has not been implemented.'); + } + + /// A [Marker] has been tapped. + Stream onMarkerTap({@required int mapId}) { + throw UnimplementedError('onMarkerTap() has not been implemented.'); + } + + /// An [InfoWindow] has been tapped. + Stream onInfoWindowTap({@required int mapId}) { + throw UnimplementedError('onInfoWindowTap() has not been implemented.'); + } + + /// A [Marker] has been dragged to a different [LatLng] position. + Stream onMarkerDragEnd({@required int mapId}) { + throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); + } + + /// A [Polyline] has been tapped. + Stream onPolylineTap({@required int mapId}) { + throw UnimplementedError('onPolylineTap() has not been implemented.'); + } + + /// A [Polygon] has been tapped. + Stream onPolygonTap({@required int mapId}) { + throw UnimplementedError('onPolygonTap() has not been implemented.'); + } + + /// A [Circle] has been tapped. + Stream onCircleTap({@required int mapId}) { + throw UnimplementedError('onCircleTap() has not been implemented.'); + } + + /// A Map has been tapped at a certain [LatLng]. + Stream onTap({@required int mapId}) { + throw UnimplementedError('onTap() has not been implemented.'); + } + + /// A Map has been long-pressed at a certain [LatLng]. + Stream onLongPress({@required int mapId}) { + throw UnimplementedError('onLongPress() has not been implemented.'); + } + + /// Returns a widget displaying the map view + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + PlatformViewCreatedCallback onPlatformViewCreated) { + throw UnimplementedError('buildView() has not been implemented.'); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart new file mode 100644 index 000000000000..40581b43e065 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart @@ -0,0 +1,116 @@ +// Copyright 2018 The Chromium 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' show Future; +import 'dart:typed_data' show Uint8List; + +import 'package:flutter/material.dart' + show ImageConfiguration, AssetImage, AssetBundleImageKey; +import 'package:flutter/services.dart' show AssetBundle; + +/// Defines a bitmap image. For a marker, this class can be used to set the +/// image of the marker icon. For a ground overlay, it can be used to set the +/// image to place on the surface of the earth. +class BitmapDescriptor { + const BitmapDescriptor._(this._json); + + /// Convenience hue value representing red. + static const double hueRed = 0.0; + + /// Convenience hue value representing orange. + static const double hueOrange = 30.0; + + /// Convenience hue value representing yellow. + static const double hueYellow = 60.0; + + /// Convenience hue value representing green. + static const double hueGreen = 120.0; + + /// Convenience hue value representing cyan. + static const double hueCyan = 180.0; + + /// Convenience hue value representing azure. + static const double hueAzure = 210.0; + + /// Convenience hue value representing blue. + static const double hueBlue = 240.0; + + /// Convenience hue value representing violet. + static const double hueViolet = 270.0; + + /// Convenience hue value representing magenta. + static const double hueMagenta = 300.0; + + /// Convenience hue value representing rose. + static const double hueRose = 330.0; + + /// Creates a BitmapDescriptor that refers to the default marker image. + static const BitmapDescriptor defaultMarker = + BitmapDescriptor._(['defaultMarker']); + + /// Creates a BitmapDescriptor that refers to a colorization of the default + /// marker image. For convenience, there is a predefined set of hue values. + /// See e.g. [hueYellow]. + static BitmapDescriptor defaultMarkerWithHue(double hue) { + assert(0.0 <= hue && hue < 360.0); + return BitmapDescriptor._(['defaultMarker', hue]); + } + + /// Creates a BitmapDescriptor using the name of a bitmap image in the assets + /// directory. + /// + /// Use [fromAssetImage]. This method does not respect the screen dpi when + /// picking an asset image. + @Deprecated("Use fromAssetImage instead") + static BitmapDescriptor fromAsset(String assetName, {String package}) { + if (package == null) { + return BitmapDescriptor._(['fromAsset', assetName]); + } else { + return BitmapDescriptor._(['fromAsset', assetName, package]); + } + } + + /// Creates a [BitmapDescriptor] from an asset image. + /// + /// Asset images in flutter are stored per: + /// https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets + /// This method takes into consideration various asset resolutions + /// and scales the images to the right resolution depending on the dpi. + /// Set `mipmaps` to false to load the exact dpi version of the image, `mipmap` is true by default. + static Future fromAssetImage( + ImageConfiguration configuration, + String assetName, { + AssetBundle bundle, + String package, + bool mipmaps = true, + }) async { + if (!mipmaps && configuration.devicePixelRatio != null) { + return BitmapDescriptor._([ + 'fromAssetImage', + assetName, + configuration.devicePixelRatio, + ]); + } + final AssetImage assetImage = + AssetImage(assetName, package: package, bundle: bundle); + final AssetBundleImageKey assetBundleImageKey = + await assetImage.obtainKey(configuration); + return BitmapDescriptor._([ + 'fromAssetImage', + assetBundleImageKey.name, + assetBundleImageKey.scale, + ]); + } + + /// Creates a BitmapDescriptor using an array of bytes that must be encoded + /// as PNG. + static BitmapDescriptor fromBytes(Uint8List byteData) { + return BitmapDescriptor._(['fromBytes', byteData]); + } + + final dynamic _json; + + /// Convert the object to a Json format. + dynamic toJson() => _json; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/callbacks.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/callbacks.dart new file mode 100644 index 000000000000..c20ece5d6c7c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/callbacks.dart @@ -0,0 +1,63 @@ +// Copyright 2018 The Chromium 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 'types.dart'; + +/// Callback that receives updates to the camera position. +/// +/// This callback is triggered when the platform Google Map +/// registers a camera movement. +/// +/// This is used in [GoogleMap.onCameraMove]. +typedef void CameraPositionCallback(CameraPosition position); + +/// Callback function taking a single argument. +typedef void ArgumentCallback(T argument); + +/// Mutable collection of [ArgumentCallback] instances, itself an [ArgumentCallback]. +/// +/// Additions and removals happening during a single [call] invocation do not +/// change who gets a callback until the next such invocation. +/// +/// Optimized for the singleton case. +class ArgumentCallbacks { + final List> _callbacks = >[]; + + /// Callback method. Invokes the corresponding method on each callback + /// in this collection. + /// + /// The list of callbacks being invoked is computed at the start of the + /// method and is unaffected by any changes subsequently made to this + /// collection. + void call(T argument) { + final int length = _callbacks.length; + if (length == 1) { + _callbacks[0].call(argument); + } else if (0 < length) { + for (ArgumentCallback callback + in List>.from(_callbacks)) { + callback(argument); + } + } + } + + /// Adds a callback to this collection. + void add(ArgumentCallback callback) { + assert(callback != null); + _callbacks.add(callback); + } + + /// Removes a callback from this collection. + /// + /// Does nothing, if the callback was not present. + void remove(ArgumentCallback callback) { + _callbacks.remove(callback); + } + + /// Whether this collection is empty. + bool get isEmpty => _callbacks.isEmpty; + + /// Whether this collection is non-empty. + bool get isNotEmpty => _callbacks.isNotEmpty; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/camera.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/camera.dart new file mode 100644 index 000000000000..10ea1e98846a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/camera.dart @@ -0,0 +1,197 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues, Offset; + +import 'package:meta/meta.dart' show required; + +import 'types.dart'; + +/// The position of the map "camera", the view point from which the world is shown in the map view. +/// +/// Aggregates the camera's [target] geographical location, its [zoom] level, +/// [tilt] angle, and [bearing]. +class CameraPosition { + /// Creates a immutable representation of the [GoogleMap] camera. + /// + /// [AssertionError] is thrown if [bearing], [target], [tilt], or [zoom] are + /// null. + const CameraPosition({ + this.bearing = 0.0, + @required this.target, + this.tilt = 0.0, + this.zoom = 0.0, + }) : assert(bearing != null), + assert(target != null), + assert(tilt != null), + assert(zoom != null); + + /// The camera's bearing in degrees, measured clockwise from north. + /// + /// A bearing of 0.0, the default, means the camera points north. + /// A bearing of 90.0 means the camera points east. + final double bearing; + + /// The geographical location that the camera is pointing at. + final LatLng target; + + /// The angle, in degrees, of the camera angle from the nadir. + /// + /// A tilt of 0.0, the default and minimum supported value, means the camera + /// is directly facing the Earth. + /// + /// The maximum tilt value depends on the current zoom level. Values beyond + /// the supported range are allowed, but on applying them to a map they will + /// be silently clamped to the supported range. + final double tilt; + + /// The zoom level of the camera. + /// + /// A zoom of 0.0, the default, means the screen width of the world is 256. + /// Adding 1.0 to the zoom level doubles the screen width of the map. So at + /// zoom level 3.0, the screen width of the world is 2³x256=2048. + /// + /// Larger zoom levels thus means the camera is placed closer to the surface + /// of the Earth, revealing more detail in a narrower geographical region. + /// + /// The supported zoom level range depends on the map data and device. Values + /// beyond the supported range are allowed, but on applying them to a map they + /// will be silently clamped to the supported range. + final double zoom; + + /// Serializes [CameraPosition]. + /// + /// Mainly for internal use when calling [CameraUpdate.newCameraPosition]. + dynamic toMap() => { + 'bearing': bearing, + 'target': target.toJson(), + 'tilt': tilt, + 'zoom': zoom, + }; + + /// Deserializes [CameraPosition] from a map. + /// + /// Mainly for internal use. + static CameraPosition fromMap(dynamic json) { + if (json == null) { + return null; + } + return CameraPosition( + bearing: json['bearing'], + target: LatLng.fromJson(json['target']), + tilt: json['tilt'], + zoom: json['zoom'], + ); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraPosition typedOther = other; + return bearing == typedOther.bearing && + target == typedOther.target && + tilt == typedOther.tilt && + zoom == typedOther.zoom; + } + + @override + int get hashCode => hashValues(bearing, target, tilt, zoom); + + @override + String toString() => + 'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; +} + +/// Defines a camera move, supporting absolute moves as well as moves relative +/// the current position. +class CameraUpdate { + CameraUpdate._(this._json); + + /// Returns a camera update that moves the camera to the specified position. + static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { + return CameraUpdate._( + ['newCameraPosition', cameraPosition.toMap()], + ); + } + + /// Returns a camera update that moves the camera target to the specified + /// geographical location. + static CameraUpdate newLatLng(LatLng latLng) { + return CameraUpdate._(['newLatLng', latLng.toJson()]); + } + + /// Returns a camera update that transforms the camera so that the specified + /// geographical bounding box is centered in the map view at the greatest + /// possible zoom level. A non-zero [padding] insets the bounding box from the + /// map view's edges. The camera's new tilt and bearing will both be 0.0. + static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { + return CameraUpdate._([ + 'newLatLngBounds', + bounds.toJson(), + padding, + ]); + } + + /// Returns a camera update that moves the camera target to the specified + /// geographical location and zoom level. + static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { + return CameraUpdate._( + ['newLatLngZoom', latLng.toJson(), zoom], + ); + } + + /// Returns a camera update that moves the camera target the specified screen + /// distance. + /// + /// For a camera with bearing 0.0 (pointing north), scrolling by 50,75 moves + /// the camera's target to a geographical location that is 50 to the east and + /// 75 to the south of the current location, measured in screen coordinates. + static CameraUpdate scrollBy(double dx, double dy) { + return CameraUpdate._( + ['scrollBy', dx, dy], + ); + } + + /// Returns a camera update that modifies the camera zoom level by the + /// specified amount. The optional [focus] is a screen point whose underlying + /// geographical location should be invariant, if possible, by the movement. + static CameraUpdate zoomBy(double amount, [Offset focus]) { + if (focus == null) { + return CameraUpdate._(['zoomBy', amount]); + } else { + return CameraUpdate._([ + 'zoomBy', + amount, + [focus.dx, focus.dy], + ]); + } + } + + /// Returns a camera update that zooms the camera in, bringing the camera + /// closer to the surface of the Earth. + /// + /// Equivalent to the result of calling `zoomBy(1.0)`. + static CameraUpdate zoomIn() { + return CameraUpdate._(['zoomIn']); + } + + /// Returns a camera update that zooms the camera out, bringing the camera + /// further away from the surface of the Earth. + /// + /// Equivalent to the result of calling `zoomBy(-1.0)`. + static CameraUpdate zoomOut() { + return CameraUpdate._(['zoomOut']); + } + + /// Returns a camera update that sets the camera zoom level. + static CameraUpdate zoomTo(double zoom) { + return CameraUpdate._(['zoomTo', zoom]); + } + + final dynamic _json; + + /// Converts this object to something serializable in JSON. + dynamic toJson() => _json; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart new file mode 100644 index 000000000000..68bf14c36408 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart @@ -0,0 +1,55 @@ +// Copyright 2019 The Chromium 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 'package:meta/meta.dart' show immutable; + +import 'types.dart'; + +/// Cap that can be applied at the start or end vertex of a [Polyline]. +@immutable +class Cap { + const Cap._(this._json); + + /// Cap that is squared off exactly at the start or end vertex of a [Polyline] + /// with solid stroke pattern, equivalent to having no additional cap beyond + /// the start or end vertex. + /// + /// This is the default cap type at start and end vertices of Polylines with + /// solid stroke pattern. + static const Cap buttCap = Cap._(['buttCap']); + + /// Cap that is a semicircle with radius equal to half the stroke width, + /// centered at the start or end vertex of a [Polyline] with solid stroke + /// pattern. + static const Cap roundCap = Cap._(['roundCap']); + + /// Cap that is squared off after extending half the stroke width beyond the + /// start or end vertex of a [Polyline] with solid stroke pattern. + static const Cap squareCap = Cap._(['squareCap']); + + /// Constructs a new CustomCap with a bitmap overlay centered at the start or + /// end vertex of a [Polyline], orientated according to the direction of the line's + /// first or last edge and scaled with respect to the line's stroke width. + /// + /// CustomCap can be applied to [Polyline] with any stroke pattern. + /// + /// [bitmapDescriptor] must not be null. + /// + /// [refWidth] is the reference stroke width (in pixels) - the stroke width for which + /// the cap bitmap at its native dimension is designed. Must be positive. Default value + /// is 10 pixels. + static Cap customCapFromBitmap( + BitmapDescriptor bitmapDescriptor, { + double refWidth = 10, + }) { + assert(bitmapDescriptor != null); + assert(refWidth > 0.0); + return Cap._(['customCap', bitmapDescriptor.toJson(), refWidth]); + } + + final dynamic _json; + + /// Converts this object to something serializable in JSON. + dynamic toJson() => _json; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle.dart new file mode 100644 index 000000000000..d1418a4c30b1 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle.dart @@ -0,0 +1,167 @@ +// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart' show VoidCallback; +import 'package:flutter/material.dart' show Color, Colors; +import 'package:meta/meta.dart' show immutable, required; + +import 'types.dart'; + +/// Uniquely identifies a [Circle] among [GoogleMap] circles. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class CircleId { + /// Creates an immutable identifier for a [Circle]. + CircleId(this.value) : assert(value != null); + + /// value of the [CircleId]. + final String value; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final CircleId typedOther = other; + return value == typedOther.value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'CircleId{value: $value}'; + } +} + +/// Draws a circle on the map. +@immutable +class Circle { + /// Creates an immutable representation of a [Circle] to draw on [GoogleMap]. + const Circle({ + @required this.circleId, + this.consumeTapEvents = false, + this.fillColor = Colors.transparent, + this.center = const LatLng(0.0, 0.0), + this.radius = 0, + this.strokeColor = Colors.black, + this.strokeWidth = 10, + this.visible = true, + this.zIndex = 0, + this.onTap, + }); + + /// Uniquely identifies a [Circle]. + final CircleId circleId; + + /// True if the [Circle] consumes tap events. + /// + /// If this is false, [onTap] callback will not be triggered. + final bool consumeTapEvents; + + /// Fill color in ARGB format, the same format used by Color. The default value is transparent (0x00000000). + final Color fillColor; + + /// Geographical location of the circle center. + final LatLng center; + + /// Radius of the circle in meters; must be positive. The default value is 0. + final double radius; + + /// Fill color in ARGB format, the same format used by Color. The default value is black (0xff000000). + final Color strokeColor; + + /// The width of the circle's outline in screen points. + /// + /// The width is constant and independent of the camera's zoom level. + /// The default value is 10. + /// Setting strokeWidth to 0 results in no stroke. + final int strokeWidth; + + /// True if the circle is visible. + final bool visible; + + /// The z-index of the circle, used to determine relative drawing order of + /// map overlays. + /// + /// Overlays are drawn in order of z-index, so that lower values means drawn + /// earlier, and thus appearing to be closer to the surface of the Earth. + final int zIndex; + + /// Callbacks to receive tap events for circle placed on this map. + final VoidCallback onTap; + + /// Creates a new [Circle] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + Circle copyWith({ + bool consumeTapEventsParam, + Color fillColorParam, + LatLng centerParam, + double radiusParam, + Color strokeColorParam, + int strokeWidthParam, + bool visibleParam, + int zIndexParam, + VoidCallback onTapParam, + }) { + return Circle( + circleId: circleId, + consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, + fillColor: fillColorParam ?? fillColor, + center: centerParam ?? center, + radius: radiusParam ?? radius, + strokeColor: strokeColorParam ?? strokeColor, + strokeWidth: strokeWidthParam ?? strokeWidth, + visible: visibleParam ?? visible, + zIndex: zIndexParam ?? zIndex, + onTap: onTapParam ?? onTap, + ); + } + + /// Creates a new [Circle] object whose values are the same as this instance. + Circle clone() => copyWith(); + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('circleId', circleId.value); + addIfPresent('consumeTapEvents', consumeTapEvents); + addIfPresent('fillColor', fillColor.value); + addIfPresent('center', center.toJson()); + addIfPresent('radius', radius); + addIfPresent('strokeColor', strokeColor.value); + addIfPresent('strokeWidth', strokeWidth); + addIfPresent('visible', visible); + addIfPresent('zIndex', zIndex); + + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final Circle typedOther = other; + return circleId == typedOther.circleId && + consumeTapEvents == typedOther.consumeTapEvents && + fillColor == typedOther.fillColor && + center == typedOther.center && + radius == typedOther.radius && + strokeColor == typedOther.strokeColor && + strokeWidth == typedOther.strokeWidth && + visible == typedOther.visible && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => circleId.hashCode; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle_updates.dart new file mode 100644 index 000000000000..6f494423a38f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle_updates.dart @@ -0,0 +1,110 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'types.dart'; +import 'utils/circle.dart'; + +/// [Circle] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class CircleUpdates { + /// Computes [CircleUpdates] given previous and current [Circle]s. + CircleUpdates.from(Set previous, Set current) { + if (previous == null) { + previous = Set.identity(); + } + + if (current == null) { + current = Set.identity(); + } + + final Map previousCircles = keyByCircleId(previous); + final Map currentCircles = keyByCircleId(current); + + final Set prevCircleIds = previousCircles.keys.toSet(); + final Set currentCircleIds = currentCircles.keys.toSet(); + + Circle idToCurrentCircle(CircleId id) { + return currentCircles[id]; + } + + final Set _circleIdsToRemove = + prevCircleIds.difference(currentCircleIds); + + final Set _circlesToAdd = currentCircleIds + .difference(prevCircleIds) + .map(idToCurrentCircle) + .toSet(); + + /// Returns `true` if [current] is not equals to previous one with the + /// same id. + bool hasChanged(Circle current) { + final Circle previous = previousCircles[current.circleId]; + return current != previous; + } + + final Set _circlesToChange = currentCircleIds + .intersection(prevCircleIds) + .map(idToCurrentCircle) + .where(hasChanged) + .toSet(); + + circlesToAdd = _circlesToAdd; + circleIdsToRemove = _circleIdsToRemove; + circlesToChange = _circlesToChange; + } + + /// Set of Circles to be added in this update. + Set circlesToAdd; + + /// Set of CircleIds to be removed in this update. + Set circleIdsToRemove; + + /// Set of Circles to be changed in this update. + Set circlesToChange; + + /// Converts this object to something serializable in JSON. + Map toJson() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('circlesToAdd', serializeCircleSet(circlesToAdd)); + addIfNonNull('circlesToChange', serializeCircleSet(circlesToChange)); + addIfNonNull('circleIdsToRemove', + circleIdsToRemove.map((CircleId m) => m.value).toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final CircleUpdates typedOther = other; + return setEquals(circlesToAdd, typedOther.circlesToAdd) && + setEquals(circleIdsToRemove, typedOther.circleIdsToRemove) && + setEquals(circlesToChange, typedOther.circlesToChange); + } + + @override + int get hashCode => + hashValues(circlesToAdd, circleIdsToRemove, circlesToChange); + + @override + String toString() { + return '_CircleUpdates{circlesToAdd: $circlesToAdd, ' + 'circleIdsToRemove: $circleIdsToRemove, ' + 'circlesToChange: $circlesToChange}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/joint_type.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/joint_type.dart new file mode 100644 index 000000000000..c7df0b298624 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/joint_type.dart @@ -0,0 +1,29 @@ +// Copyright 2019 The Chromium 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 'package:meta/meta.dart' show immutable; + +/// Joint types for [Polyline]. +@immutable +class JointType { + const JointType._(this.value); + + /// The value representing the [JointType] on the sdk. + final int value; + + /// Mitered joint, with fixed pointed extrusion equal to half the stroke width on the outside of the joint. + /// + /// Constant Value: 0 + static const JointType mitered = JointType._(0); + + /// Flat bevel on the outside of the joint. + /// + /// Constant Value: 1 + static const JointType bevel = JointType._(1); + + /// Rounded on the outside of the joint by an arc of radius equal to half the stroke width, centered at the vertex. + /// + /// Constant Value: 2 + static const JointType round = JointType._(2); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart new file mode 100644 index 000000000000..6b76a6d496ac --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart @@ -0,0 +1,129 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'package:meta/meta.dart'; + +/// A pair of latitude and longitude coordinates, stored as degrees. +class LatLng { + /// Creates a geographical location specified in degrees [latitude] and + /// [longitude]. + /// + /// The latitude is clamped to the inclusive interval from -90.0 to +90.0. + /// + /// The longitude is normalized to the half-open interval from -180.0 + /// (inclusive) to +180.0 (exclusive) + const LatLng(double latitude, double longitude) + : assert(latitude != null), + assert(longitude != null), + latitude = + (latitude < -90.0 ? -90.0 : (90.0 < latitude ? 90.0 : latitude)), + longitude = (longitude + 180.0) % 360.0 - 180.0; + + /// The latitude in degrees between -90.0 and 90.0, both inclusive. + final double latitude; + + /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive). + final double longitude; + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + return [latitude, longitude]; + } + + /// Initialize a LatLng from an \[lat, lng\] array. + static LatLng fromJson(dynamic json) { + if (json == null) { + return null; + } + return LatLng(json[0], json[1]); + } + + @override + String toString() => '$runtimeType($latitude, $longitude)'; + + @override + bool operator ==(Object o) { + return o is LatLng && o.latitude == latitude && o.longitude == longitude; + } + + @override + int get hashCode => hashValues(latitude, longitude); +} + +/// A latitude/longitude aligned rectangle. +/// +/// The rectangle conceptually includes all points (lat, lng) where +/// * lat ∈ [`southwest.latitude`, `northeast.latitude`] +/// * lng ∈ [`southwest.longitude`, `northeast.longitude`], +/// if `southwest.longitude` ≤ `northeast.longitude`, +/// * lng ∈ [-180, `northeast.longitude`] ∪ [`southwest.longitude`, 180], +/// if `northeast.longitude` < `southwest.longitude` +class LatLngBounds { + /// Creates geographical bounding box with the specified corners. + /// + /// The latitude of the southwest corner cannot be larger than the + /// latitude of the northeast corner. + LatLngBounds({@required this.southwest, @required this.northeast}) + : assert(southwest != null), + assert(northeast != null), + assert(southwest.latitude <= northeast.latitude); + + /// The southwest corner of the rectangle. + final LatLng southwest; + + /// The northeast corner of the rectangle. + final LatLng northeast; + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + return [southwest.toJson(), northeast.toJson()]; + } + + /// Returns whether this rectangle contains the given [LatLng]. + bool contains(LatLng point) { + return _containsLatitude(point.latitude) && + _containsLongitude(point.longitude); + } + + bool _containsLatitude(double lat) { + return (southwest.latitude <= lat) && (lat <= northeast.latitude); + } + + bool _containsLongitude(double lng) { + if (southwest.longitude <= northeast.longitude) { + return southwest.longitude <= lng && lng <= northeast.longitude; + } else { + return southwest.longitude <= lng || lng <= northeast.longitude; + } + } + + /// Converts a list to [LatLngBounds]. + @visibleForTesting + static LatLngBounds fromList(dynamic json) { + if (json == null) { + return null; + } + return LatLngBounds( + southwest: LatLng.fromJson(json[0]), + northeast: LatLng.fromJson(json[1]), + ); + } + + @override + String toString() { + return '$runtimeType($southwest, $northeast)'; + } + + @override + bool operator ==(Object o) { + return o is LatLngBounds && + o.southwest == southwest && + o.northeast == northeast; + } + + @override + int get hashCode => hashValues(southwest, northeast); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart new file mode 100644 index 000000000000..9b57f9676334 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart @@ -0,0 +1,324 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues, Offset; + +import 'package:flutter/foundation.dart' show ValueChanged, VoidCallback; +import 'package:meta/meta.dart' show immutable, required; + +import 'types.dart'; + +dynamic _offsetToJson(Offset offset) { + if (offset == null) { + return null; + } + return [offset.dx, offset.dy]; +} + +/// Text labels for a [Marker] info window. +class InfoWindow { + /// Creates an immutable representation of a label on for [Marker]. + const InfoWindow({ + this.title, + this.snippet, + this.anchor = const Offset(0.5, 0.0), + this.onTap, + }); + + /// Text labels specifying that no text is to be displayed. + static const InfoWindow noText = InfoWindow(); + + /// Text displayed in an info window when the user taps the marker. + /// + /// A null value means no title. + final String title; + + /// Additional text displayed below the [title]. + /// + /// A null value means no additional text. + final String snippet; + + /// The icon image point that will be the anchor of the info window when + /// displayed. + /// + /// The image point is specified in normalized coordinates: An anchor of + /// (0.0, 0.0) means the top left corner of the image. An anchor + /// of (1.0, 1.0) means the bottom right corner of the image. + final Offset anchor; + + /// onTap callback for this [InfoWindow]. + final VoidCallback onTap; + + /// Creates a new [InfoWindow] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + InfoWindow copyWith({ + String titleParam, + String snippetParam, + Offset anchorParam, + VoidCallback onTapParam, + }) { + return InfoWindow( + title: titleParam ?? title, + snippet: snippetParam ?? snippet, + anchor: anchorParam ?? anchor, + onTap: onTapParam ?? onTap, + ); + } + + dynamic _toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('title', title); + addIfPresent('snippet', snippet); + addIfPresent('anchor', _offsetToJson(anchor)); + + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final InfoWindow typedOther = other; + return title == typedOther.title && + snippet == typedOther.snippet && + anchor == typedOther.anchor; + } + + @override + int get hashCode => hashValues(title.hashCode, snippet, anchor); + + @override + String toString() { + return 'InfoWindow{title: $title, snippet: $snippet, anchor: $anchor}'; + } +} + +/// Uniquely identifies a [Marker] among [GoogleMap] markers. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class MarkerId { + /// Creates an immutable identifier for a [Marker]. + MarkerId(this.value) : assert(value != null); + + /// value of the [MarkerId]. + final String value; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final MarkerId typedOther = other; + return value == typedOther.value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'MarkerId{value: $value}'; + } +} + +/// Marks a geographical location on the map. +/// +/// A marker icon is drawn oriented against the device's screen rather than +/// the map's surface; that is, it will not necessarily change orientation +/// due to map rotations, tilting, or zooming. +@immutable +class Marker { + /// Creates a set of marker configuration options. + /// + /// Default marker options. + /// + /// Specifies a marker that + /// * is fully opaque; [alpha] is 1.0 + /// * uses icon bottom center to indicate map position; [anchor] is (0.5, 1.0) + /// * has default tap handling; [consumeTapEvents] is false + /// * is stationary; [draggable] is false + /// * is drawn against the screen, not the map; [flat] is false + /// * has a default icon; [icon] is `BitmapDescriptor.defaultMarker` + /// * anchors the info window at top center; [infoWindowAnchor] is (0.5, 0.0) + /// * has no info window text; [infoWindowText] is `InfoWindowText.noText` + /// * is positioned at 0, 0; [position] is `LatLng(0.0, 0.0)` + /// * has an axis-aligned icon; [rotation] is 0.0 + /// * is visible; [visible] is true + /// * is placed at the base of the drawing order; [zIndex] is 0.0 + /// * reports [onTap] events + /// * reports [onDragEnd] events + const Marker({ + @required this.markerId, + this.alpha = 1.0, + this.anchor = const Offset(0.5, 1.0), + this.consumeTapEvents = false, + this.draggable = false, + this.flat = false, + this.icon = BitmapDescriptor.defaultMarker, + this.infoWindow = InfoWindow.noText, + this.position = const LatLng(0.0, 0.0), + this.rotation = 0.0, + this.visible = true, + this.zIndex = 0.0, + this.onTap, + this.onDragEnd, + }) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0)); + + /// Uniquely identifies a [Marker]. + final MarkerId markerId; + + /// The opacity of the marker, between 0.0 and 1.0 inclusive. + /// + /// 0.0 means fully transparent, 1.0 means fully opaque. + final double alpha; + + /// The icon image point that will be placed at the [position] of the marker. + /// + /// The image point is specified in normalized coordinates: An anchor of + /// (0.0, 0.0) means the top left corner of the image. An anchor + /// of (1.0, 1.0) means the bottom right corner of the image. + final Offset anchor; + + /// True if the marker icon consumes tap events. If not, the map will perform + /// default tap handling by centering the map on the marker and displaying its + /// info window. + final bool consumeTapEvents; + + /// True if the marker is draggable by user touch events. + final bool draggable; + + /// True if the marker is rendered flatly against the surface of the Earth, so + /// that it will rotate and tilt along with map camera movements. + final bool flat; + + /// A description of the bitmap used to draw the marker icon. + final BitmapDescriptor icon; + + /// A Google Maps InfoWindow. + /// + /// The window is displayed when the marker is tapped. + final InfoWindow infoWindow; + + /// Geographical location of the marker. + final LatLng position; + + /// Rotation of the marker image in degrees clockwise from the [anchor] point. + final double rotation; + + /// True if the marker is visible. + final bool visible; + + /// The z-index of the marker, used to determine relative drawing order of + /// map overlays. + /// + /// Overlays are drawn in order of z-index, so that lower values means drawn + /// earlier, and thus appearing to be closer to the surface of the Earth. + final double zIndex; + + /// Callbacks to receive tap events for markers placed on this map. + final VoidCallback onTap; + + /// Signature reporting the new [LatLng] at the end of a drag event. + final ValueChanged onDragEnd; + + /// Creates a new [Marker] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + Marker copyWith({ + double alphaParam, + Offset anchorParam, + bool consumeTapEventsParam, + bool draggableParam, + bool flatParam, + BitmapDescriptor iconParam, + InfoWindow infoWindowParam, + LatLng positionParam, + double rotationParam, + bool visibleParam, + double zIndexParam, + VoidCallback onTapParam, + ValueChanged onDragEndParam, + }) { + return Marker( + markerId: markerId, + alpha: alphaParam ?? alpha, + anchor: anchorParam ?? anchor, + consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, + draggable: draggableParam ?? draggable, + flat: flatParam ?? flat, + icon: iconParam ?? icon, + infoWindow: infoWindowParam ?? infoWindow, + position: positionParam ?? position, + rotation: rotationParam ?? rotation, + visible: visibleParam ?? visible, + zIndex: zIndexParam ?? zIndex, + onTap: onTapParam ?? onTap, + onDragEnd: onDragEndParam ?? onDragEnd, + ); + } + + /// Creates a new [Marker] object whose values are the same as this instance. + Marker clone() => copyWith(); + + /// Converts this object to something serializable in JSON. + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('markerId', markerId.value); + addIfPresent('alpha', alpha); + addIfPresent('anchor', _offsetToJson(anchor)); + addIfPresent('consumeTapEvents', consumeTapEvents); + addIfPresent('draggable', draggable); + addIfPresent('flat', flat); + addIfPresent('icon', icon?.toJson()); + addIfPresent('infoWindow', infoWindow?._toJson()); + addIfPresent('position', position?.toJson()); + addIfPresent('rotation', rotation); + addIfPresent('visible', visible); + addIfPresent('zIndex', zIndex); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final Marker typedOther = other; + return markerId == typedOther.markerId && + alpha == typedOther.alpha && + anchor == typedOther.anchor && + consumeTapEvents == typedOther.consumeTapEvents && + draggable == typedOther.draggable && + flat == typedOther.flat && + icon == typedOther.icon && + infoWindow == typedOther.infoWindow && + position == typedOther.position && + rotation == typedOther.rotation && + visible == typedOther.visible && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => markerId.hashCode; + + @override + String toString() { + return 'Marker{markerId: $markerId, alpha: $alpha, anchor: $anchor, ' + 'consumeTapEvents: $consumeTapEvents, draggable: $draggable, flat: $flat, ' + 'icon: $icon, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' + 'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker_updates.dart new file mode 100644 index 000000000000..bb6ea8813ea3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker_updates.dart @@ -0,0 +1,110 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'types.dart'; +import 'utils/marker.dart'; + +/// [Marker] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class MarkerUpdates { + /// Computes [MarkerUpdates] given previous and current [Marker]s. + MarkerUpdates.from(Set previous, Set current) { + if (previous == null) { + previous = Set.identity(); + } + + if (current == null) { + current = Set.identity(); + } + + final Map previousMarkers = keyByMarkerId(previous); + final Map currentMarkers = keyByMarkerId(current); + + final Set prevMarkerIds = previousMarkers.keys.toSet(); + final Set currentMarkerIds = currentMarkers.keys.toSet(); + + Marker idToCurrentMarker(MarkerId id) { + return currentMarkers[id]; + } + + final Set _markerIdsToRemove = + prevMarkerIds.difference(currentMarkerIds); + + final Set _markersToAdd = currentMarkerIds + .difference(prevMarkerIds) + .map(idToCurrentMarker) + .toSet(); + + /// Returns `true` if [current] is not equals to previous one with the + /// same id. + bool hasChanged(Marker current) { + final Marker previous = previousMarkers[current.markerId]; + return current != previous; + } + + final Set _markersToChange = currentMarkerIds + .intersection(prevMarkerIds) + .map(idToCurrentMarker) + .where(hasChanged) + .toSet(); + + markersToAdd = _markersToAdd; + markerIdsToRemove = _markerIdsToRemove; + markersToChange = _markersToChange; + } + + /// Set of Markers to be added in this update. + Set markersToAdd; + + /// Set of MarkerIds to be removed in this update. + Set markerIdsToRemove; + + /// Set of Markers to be changed in this update. + Set markersToChange; + + /// Converts this object to something serializable in JSON. + Map toJson() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('markersToAdd', serializeMarkerSet(markersToAdd)); + addIfNonNull('markersToChange', serializeMarkerSet(markersToChange)); + addIfNonNull('markerIdsToRemove', + markerIdsToRemove.map((MarkerId m) => m.value).toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final MarkerUpdates typedOther = other; + return setEquals(markersToAdd, typedOther.markersToAdd) && + setEquals(markerIdsToRemove, typedOther.markerIdsToRemove) && + setEquals(markersToChange, typedOther.markersToChange); + } + + @override + int get hashCode => + hashValues(markersToAdd, markerIdsToRemove, markersToChange); + + @override + String toString() { + return '_MarkerUpdates{markersToAdd: $markersToAdd, ' + 'markerIdsToRemove: $markerIdsToRemove, ' + 'markersToChange: $markersToChange}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/pattern_item.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/pattern_item.dart new file mode 100644 index 000000000000..28c7ce9d33dd --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/pattern_item.dart @@ -0,0 +1,35 @@ +// Copyright 2019 The Chromium 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 'package:meta/meta.dart' show immutable; + +/// Item used in the stroke pattern for a Polyline. +@immutable +class PatternItem { + const PatternItem._(this._json); + + /// A dot used in the stroke pattern for a [Polyline]. + static const PatternItem dot = PatternItem._(['dot']); + + /// A dash used in the stroke pattern for a [Polyline]. + /// + /// [length] has to be non-negative. + static PatternItem dash(double length) { + assert(length >= 0.0); + return PatternItem._(['dash', length]); + } + + /// A gap used in the stroke pattern for a [Polyline]. + /// + /// [length] has to be non-negative. + static PatternItem gap(double length) { + assert(length >= 0.0); + return PatternItem._(['gap', length]); + } + + final dynamic _json; + + /// Converts this object to something serializable in JSON. + dynamic toJson() => _json; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon.dart new file mode 100644 index 000000000000..3b5e25060faf --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon.dart @@ -0,0 +1,186 @@ +// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart' show listEquals, VoidCallback; +import 'package:flutter/material.dart' show Color, Colors; +import 'package:meta/meta.dart' show immutable, required; + +import 'types.dart'; + +/// Uniquely identifies a [Polygon] among [GoogleMap] polygons. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class PolygonId { + /// Creates an immutable identifier for a [Polygon]. + PolygonId(this.value) : assert(value != null); + + /// value of the [PolygonId]. + final String value; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final PolygonId typedOther = other; + return value == typedOther.value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'PolygonId{value: $value}'; + } +} + +/// Draws a polygon through geographical locations on the map. +@immutable +class Polygon { + /// Creates an immutable representation of a polygon through geographical locations on the map. + const Polygon({ + @required this.polygonId, + this.consumeTapEvents = false, + this.fillColor = Colors.black, + this.geodesic = false, + this.points = const [], + this.strokeColor = Colors.black, + this.strokeWidth = 10, + this.visible = true, + this.zIndex = 0, + this.onTap, + }); + + /// Uniquely identifies a [Polygon]. + final PolygonId polygonId; + + /// True if the [Polygon] consumes tap events. + /// + /// If this is false, [onTap] callback will not be triggered. + final bool consumeTapEvents; + + /// Fill color in ARGB format, the same format used by Color. The default value is black (0xff000000). + final Color fillColor; + + /// Indicates whether the segments of the polygon should be drawn as geodesics, as opposed to straight lines + /// on the Mercator projection. + /// + /// A geodesic is the shortest path between two points on the Earth's surface. + /// The geodesic curve is constructed assuming the Earth is a sphere + final bool geodesic; + + /// The vertices of the polygon to be drawn. + /// + /// Line segments are drawn between consecutive points. A polygon is not closed by + /// default; to form a closed polygon, the start and end points must be the same. + final List points; + + /// True if the marker is visible. + final bool visible; + + /// Line color in ARGB format, the same format used by Color. The default value is black (0xff000000). + final Color strokeColor; + + /// Width of the polygon, used to define the width of the line to be drawn. + /// + /// The width is constant and independent of the camera's zoom level. + /// The default value is 10. + final int strokeWidth; + + /// The z-index of the polygon, used to determine relative drawing order of + /// map overlays. + /// + /// Overlays are drawn in order of z-index, so that lower values means drawn + /// earlier, and thus appearing to be closer to the surface of the Earth. + final int zIndex; + + /// Callbacks to receive tap events for polygon placed on this map. + final VoidCallback onTap; + + /// Creates a new [Polygon] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + Polygon copyWith({ + bool consumeTapEventsParam, + Color fillColorParam, + bool geodesicParam, + List pointsParam, + Color strokeColorParam, + int strokeWidthParam, + bool visibleParam, + int zIndexParam, + VoidCallback onTapParam, + }) { + return Polygon( + polygonId: polygonId, + consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, + fillColor: fillColorParam ?? fillColor, + geodesic: geodesicParam ?? geodesic, + points: pointsParam ?? points, + strokeColor: strokeColorParam ?? strokeColor, + strokeWidth: strokeWidthParam ?? strokeWidth, + visible: visibleParam ?? visible, + onTap: onTapParam ?? onTap, + zIndex: zIndexParam ?? zIndex, + ); + } + + /// Creates a new [Polygon] object whose values are the same as this instance. + Polygon clone() { + return copyWith(pointsParam: List.of(points)); + } + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('polygonId', polygonId.value); + addIfPresent('consumeTapEvents', consumeTapEvents); + addIfPresent('fillColor', fillColor.value); + addIfPresent('geodesic', geodesic); + addIfPresent('strokeColor', strokeColor.value); + addIfPresent('strokeWidth', strokeWidth); + addIfPresent('visible', visible); + addIfPresent('zIndex', zIndex); + + if (points != null) { + json['points'] = _pointsToJson(); + } + + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final Polygon typedOther = other; + return polygonId == typedOther.polygonId && + consumeTapEvents == typedOther.consumeTapEvents && + fillColor == typedOther.fillColor && + geodesic == typedOther.geodesic && + listEquals(points, typedOther.points) && + visible == typedOther.visible && + strokeColor == typedOther.strokeColor && + strokeWidth == typedOther.strokeWidth && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => polygonId.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon_updates.dart new file mode 100644 index 000000000000..cc8b8e26c896 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon_updates.dart @@ -0,0 +1,110 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'types.dart'; +import 'utils/polygon.dart'; + +/// [Polygon] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class PolygonUpdates { + /// Computes [PolygonUpdates] given previous and current [Polygon]s. + PolygonUpdates.from(Set previous, Set current) { + if (previous == null) { + previous = Set.identity(); + } + + if (current == null) { + current = Set.identity(); + } + + final Map previousPolygons = keyByPolygonId(previous); + final Map currentPolygons = keyByPolygonId(current); + + final Set prevPolygonIds = previousPolygons.keys.toSet(); + final Set currentPolygonIds = currentPolygons.keys.toSet(); + + Polygon idToCurrentPolygon(PolygonId id) { + return currentPolygons[id]; + } + + final Set _polygonIdsToRemove = + prevPolygonIds.difference(currentPolygonIds); + + final Set _polygonsToAdd = currentPolygonIds + .difference(prevPolygonIds) + .map(idToCurrentPolygon) + .toSet(); + + /// Returns `true` if [current] is not equals to previous one with the + /// same id. + bool hasChanged(Polygon current) { + final Polygon previous = previousPolygons[current.polygonId]; + return current != previous; + } + + final Set _polygonsToChange = currentPolygonIds + .intersection(prevPolygonIds) + .map(idToCurrentPolygon) + .where(hasChanged) + .toSet(); + + polygonsToAdd = _polygonsToAdd; + polygonIdsToRemove = _polygonIdsToRemove; + polygonsToChange = _polygonsToChange; + } + + /// Set of Polygons to be added in this update. + Set polygonsToAdd; + + /// Set of PolygonIds to be removed in this update. + Set polygonIdsToRemove; + + /// Set of Polygons to be changed in this update. + Set polygonsToChange; + + /// Converts this object to something serializable in JSON. + Map toJson() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polygonsToAdd', serializePolygonSet(polygonsToAdd)); + addIfNonNull('polygonsToChange', serializePolygonSet(polygonsToChange)); + addIfNonNull('polygonIdsToRemove', + polygonIdsToRemove.map((PolygonId m) => m.value).toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final PolygonUpdates typedOther = other; + return setEquals(polygonsToAdd, typedOther.polygonsToAdd) && + setEquals(polygonIdsToRemove, typedOther.polygonIdsToRemove) && + setEquals(polygonsToChange, typedOther.polygonsToChange); + } + + @override + int get hashCode => + hashValues(polygonsToAdd, polygonIdsToRemove, polygonsToChange); + + @override + String toString() { + return '_PolygonUpdates{polygonsToAdd: $polygonsToAdd, ' + 'polygonIdsToRemove: $polygonIdsToRemove, ' + 'polygonsToChange: $polygonsToChange}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart new file mode 100644 index 000000000000..ae5c3b976352 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart @@ -0,0 +1,247 @@ +// Copyright 2019 The Chromium 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 'package:flutter/foundation.dart' show listEquals, VoidCallback; +import 'package:flutter/material.dart' show Color, Colors; +import 'package:meta/meta.dart' show immutable, required; + +import 'types.dart'; + +/// Uniquely identifies a [Polyline] among [GoogleMap] polylines. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class PolylineId { + /// Creates an immutable object representing a [PolylineId] among [GoogleMap] polylines. + /// + /// An [AssertionError] will be thrown if [value] is null. + PolylineId(this.value) : assert(value != null); + + /// value of the [PolylineId]. + final String value; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final PolylineId typedOther = other; + return value == typedOther.value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'PolylineId{value: $value}'; + } +} + +/// Draws a line through geographical locations on the map. +@immutable +class Polyline { + /// Creates an immutable object representing a line drawn through geographical locations on the map. + const Polyline({ + @required this.polylineId, + this.consumeTapEvents = false, + this.color = Colors.black, + this.endCap = Cap.buttCap, + this.geodesic = false, + this.jointType = JointType.mitered, + this.points = const [], + this.patterns = const [], + this.startCap = Cap.buttCap, + this.visible = true, + this.width = 10, + this.zIndex = 0, + this.onTap, + }); + + /// Uniquely identifies a [Polyline]. + final PolylineId polylineId; + + /// True if the [Polyline] consumes tap events. + /// + /// If this is false, [onTap] callback will not be triggered. + final bool consumeTapEvents; + + /// Line segment color in ARGB format, the same format used by Color. The default value is black (0xff000000). + final Color color; + + /// Indicates whether the segments of the polyline should be drawn as geodesics, as opposed to straight lines + /// on the Mercator projection. + /// + /// A geodesic is the shortest path between two points on the Earth's surface. + /// The geodesic curve is constructed assuming the Earth is a sphere + final bool geodesic; + + /// Joint type of the polyline line segments. + /// + /// The joint type defines the shape to be used when joining adjacent line segments at all vertices of the + /// polyline except the start and end vertices. See [JointType] for supported joint types. The default value is + /// mitered. + /// + /// Supported on Android only. + final JointType jointType; + + /// The stroke pattern for the polyline. + /// + /// Solid or a sequence of PatternItem objects to be repeated along the line. + /// Available PatternItem types: Gap (defined by gap length in pixels), Dash (defined by line width and dash + /// length in pixels) and Dot (circular, centered on the line, diameter defined by line width in pixels). + final List patterns; + + /// The vertices of the polyline to be drawn. + /// + /// Line segments are drawn between consecutive points. A polyline is not closed by + /// default; to form a closed polyline, the start and end points must be the same. + final List points; + + /// The cap at the start vertex of the polyline. + /// + /// The default start cap is ButtCap. + /// + /// Supported on Android only. + final Cap startCap; + + /// The cap at the end vertex of the polyline. + /// + /// The default end cap is ButtCap. + /// + /// Supported on Android only. + final Cap endCap; + + /// True if the marker is visible. + final bool visible; + + /// Width of the polyline, used to define the width of the line segment to be drawn. + /// + /// The width is constant and independent of the camera's zoom level. + /// The default value is 10. + final int width; + + /// The z-index of the polyline, used to determine relative drawing order of + /// map overlays. + /// + /// Overlays are drawn in order of z-index, so that lower values means drawn + /// earlier, and thus appearing to be closer to the surface of the Earth. + final int zIndex; + + /// Callbacks to receive tap events for polyline placed on this map. + final VoidCallback onTap; + + /// Creates a new [Polyline] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + Polyline copyWith({ + Color colorParam, + bool consumeTapEventsParam, + Cap endCapParam, + bool geodesicParam, + JointType jointTypeParam, + List patternsParam, + List pointsParam, + Cap startCapParam, + bool visibleParam, + int widthParam, + int zIndexParam, + VoidCallback onTapParam, + }) { + return Polyline( + polylineId: polylineId, + color: colorParam ?? color, + consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, + endCap: endCapParam ?? endCap, + geodesic: geodesicParam ?? geodesic, + jointType: jointTypeParam ?? jointType, + patterns: patternsParam ?? patterns, + points: pointsParam ?? points, + startCap: startCapParam ?? startCap, + visible: visibleParam ?? visible, + width: widthParam ?? width, + onTap: onTapParam ?? onTap, + zIndex: zIndexParam ?? zIndex, + ); + } + + /// Creates a new [Polyline] object whose values are the same as this + /// instance. + Polyline clone() { + return copyWith( + patternsParam: List.of(patterns), + pointsParam: List.of(points), + ); + } + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('polylineId', polylineId.value); + addIfPresent('consumeTapEvents', consumeTapEvents); + addIfPresent('color', color.value); + addIfPresent('endCap', endCap?.toJson()); + addIfPresent('geodesic', geodesic); + addIfPresent('jointType', jointType?.value); + addIfPresent('startCap', startCap?.toJson()); + addIfPresent('visible', visible); + addIfPresent('width', width); + addIfPresent('zIndex', zIndex); + + if (points != null) { + json['points'] = _pointsToJson(); + } + + if (patterns != null) { + json['pattern'] = _patternToJson(); + } + + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final Polyline typedOther = other; + return polylineId == typedOther.polylineId && + consumeTapEvents == typedOther.consumeTapEvents && + color == typedOther.color && + geodesic == typedOther.geodesic && + jointType == typedOther.jointType && + listEquals(patterns, typedOther.patterns) && + listEquals(points, typedOther.points) && + startCap == typedOther.startCap && + endCap == typedOther.endCap && + visible == typedOther.visible && + width == typedOther.width && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => polylineId.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } + + dynamic _patternToJson() { + final List result = []; + for (final PatternItem patternItem in patterns) { + if (patternItem != null) { + result.add(patternItem.toJson()); + } + } + return result; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline_updates.dart new file mode 100644 index 000000000000..f871928c0ac4 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline_updates.dart @@ -0,0 +1,111 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'utils/polyline.dart'; +import 'types.dart'; + +/// [Polyline] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class PolylineUpdates { + /// Computes [PolylineUpdates] given previous and current [Polyline]s. + PolylineUpdates.from(Set previous, Set current) { + if (previous == null) { + previous = Set.identity(); + } + + if (current == null) { + current = Set.identity(); + } + + final Map previousPolylines = + keyByPolylineId(previous); + final Map currentPolylines = keyByPolylineId(current); + + final Set prevPolylineIds = previousPolylines.keys.toSet(); + final Set currentPolylineIds = currentPolylines.keys.toSet(); + + Polyline idToCurrentPolyline(PolylineId id) { + return currentPolylines[id]; + } + + final Set _polylineIdsToRemove = + prevPolylineIds.difference(currentPolylineIds); + + final Set _polylinesToAdd = currentPolylineIds + .difference(prevPolylineIds) + .map(idToCurrentPolyline) + .toSet(); + + /// Returns `true` if [current] is not equals to previous one with the + /// same id. + bool hasChanged(Polyline current) { + final Polyline previous = previousPolylines[current.polylineId]; + return current != previous; + } + + final Set _polylinesToChange = currentPolylineIds + .intersection(prevPolylineIds) + .map(idToCurrentPolyline) + .where(hasChanged) + .toSet(); + + polylinesToAdd = _polylinesToAdd; + polylineIdsToRemove = _polylineIdsToRemove; + polylinesToChange = _polylinesToChange; + } + + /// Set of Polylines to be added in this update. + Set polylinesToAdd; + + /// Set of PolylineIds to be removed in this update. + Set polylineIdsToRemove; + + /// Set of Polylines to be changed in this update. + Set polylinesToChange; + + /// Converts this object to something serializable in JSON. + Map toJson() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polylinesToAdd', serializePolylineSet(polylinesToAdd)); + addIfNonNull('polylinesToChange', serializePolylineSet(polylinesToChange)); + addIfNonNull('polylineIdsToRemove', + polylineIdsToRemove.map((PolylineId m) => m.value).toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + final PolylineUpdates typedOther = other; + return setEquals(polylinesToAdd, typedOther.polylinesToAdd) && + setEquals(polylineIdsToRemove, typedOther.polylineIdsToRemove) && + setEquals(polylinesToChange, typedOther.polylinesToChange); + } + + @override + int get hashCode => + hashValues(polylinesToAdd, polylineIdsToRemove, polylinesToChange); + + @override + String toString() { + return '_PolylineUpdates{polylinesToAdd: $polylinesToAdd, ' + 'polylineIdsToRemove: $polylineIdsToRemove, ' + 'polylinesToChange: $polylinesToChange}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/screen_coordinate.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/screen_coordinate.dart new file mode 100644 index 000000000000..965db7969bc2 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/screen_coordinate.dart @@ -0,0 +1,46 @@ +// Copyright 2019 The Chromium 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:ui' show hashValues; + +import 'package:meta/meta.dart' show immutable, required; + +/// Represents a point coordinate in the [GoogleMap]'s view. +/// +/// The screen location is specified in screen pixels (not display pixels) relative +/// to the top left of the map, not top left of the whole screen. (x, y) = (0, 0) +/// corresponds to top-left of the [GoogleMap] not the whole screen. +@immutable +class ScreenCoordinate { + /// Creates an immutable representation of a point coordinate in the [GoogleMap]'s view. + const ScreenCoordinate({ + @required this.x, + @required this.y, + }); + + /// Represents the number of pixels from the left of the [GoogleMap]. + final int x; + + /// Represents the number of pixels from the top of the [GoogleMap]. + final int y; + + /// Converts this object to something serializable in JSON. + dynamic toJson() { + return { + "x": x, + "y": y, + }; + } + + @override + String toString() => '$runtimeType($x, $y)'; + + @override + bool operator ==(Object o) { + return o is ScreenCoordinate && o.x == x && o.y == y; + } + + @override + int get hashCode => hashValues(x, y); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart new file mode 100644 index 000000000000..e56c3a5dd646 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart @@ -0,0 +1,28 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// All the public types exposed by this package. +export 'bitmap.dart'; +export 'callbacks.dart'; +export 'camera.dart'; +export 'cap.dart'; +export 'circle_updates.dart'; +export 'circle.dart'; +export 'joint_type.dart'; +export 'location.dart'; +export 'marker_updates.dart'; +export 'marker.dart'; +export 'pattern_item.dart'; +export 'polygon_updates.dart'; +export 'polygon.dart'; +export 'polyline_updates.dart'; +export 'polyline.dart'; +export 'screen_coordinate.dart'; +export 'ui.dart'; + +// Export the utils, they're used by the Widget +export 'utils/circle.dart'; +export 'utils/marker.dart'; +export 'utils/polygon.dart'; +export 'utils/polyline.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ui.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ui.dart new file mode 100644 index 000000000000..8d84171bac03 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ui.dart @@ -0,0 +1,118 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; + +import 'types.dart'; + +/// Type of map tiles to display. +// Enum constants must be indexed to match the corresponding int constants of +// the Android platform API, see +// +enum MapType { + /// Do not display map tiles. + none, + + /// Normal tiles (traffic and labels, subtle terrain information). + normal, + + /// Satellite imaging tiles (aerial photos) + satellite, + + /// Terrain tiles (indicates type and height of terrain) + terrain, + + /// Hybrid tiles (satellite images with some labels/overlays) + hybrid, +} + +/// Bounds for the map camera target. +// Used with [GoogleMapOptions] to wrap a [LatLngBounds] value. This allows +// distinguishing between specifying an unbounded target (null `LatLngBounds`) +// from not specifying anything (null `CameraTargetBounds`). +class CameraTargetBounds { + /// Creates a camera target bounds with the specified bounding box, or null + /// to indicate that the camera target is not bounded. + const CameraTargetBounds(this.bounds); + + /// The geographical bounding box for the map camera target. + /// + /// A null value means the camera target is unbounded. + final LatLngBounds bounds; + + /// Unbounded camera target. + static const CameraTargetBounds unbounded = CameraTargetBounds(null); + + /// Converts this object to something serializable in JSON. + dynamic toJson() => [bounds?.toJson()]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraTargetBounds typedOther = other; + return bounds == typedOther.bounds; + } + + @override + int get hashCode => bounds.hashCode; + + @override + String toString() { + return 'CameraTargetBounds(bounds: $bounds)'; + } +} + +/// Preferred bounds for map camera zoom level. +// Used with [GoogleMapOptions] to wrap min and max zoom. This allows +// distinguishing between specifying unbounded zooming (null `minZoom` and +// `maxZoom`) from not specifying anything (null `MinMaxZoomPreference`). +class MinMaxZoomPreference { + /// Creates a immutable representation of the preferred minimum and maximum zoom values for the map camera. + /// + /// [AssertionError] will be thrown if [minZoom] > [maxZoom]. + const MinMaxZoomPreference(this.minZoom, this.maxZoom) + : assert(minZoom == null || maxZoom == null || minZoom <= maxZoom); + + /// The preferred minimum zoom level or null, if unbounded from below. + final double minZoom; + + /// The preferred maximum zoom level or null, if unbounded from above. + final double maxZoom; + + /// Unbounded zooming. + static const MinMaxZoomPreference unbounded = + MinMaxZoomPreference(null, null); + + /// Converts this object to something serializable in JSON. + dynamic toJson() => [minZoom, maxZoom]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final MinMaxZoomPreference typedOther = other; + return minZoom == typedOther.minZoom && maxZoom == typedOther.maxZoom; + } + + @override + int get hashCode => hashValues(minZoom, maxZoom); + + @override + String toString() { + return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; + } +} + +/// Exception when a map style is invalid or was unable to be set. +/// +/// See also: `setStyle` on [GoogleMapController] for why this exception +/// might be thrown. +class MapStyleException implements Exception { + /// Default constructor for [MapStyleException]. + const MapStyleException(this.cause); + + /// The reason `GoogleMapController.setStyle` would throw this exception. + final String cause; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/circle.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/circle.dart new file mode 100644 index 000000000000..5c3af96f8e02 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/circle.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium 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 '../types.dart'; + +/// Converts an [Iterable] of Circles in a Map of CircleId -> Circle. +Map keyByCircleId(Iterable circles) { + if (circles == null) { + return {}; + } + return Map.fromEntries(circles.map((Circle circle) => + MapEntry(circle.circleId, circle.clone()))); +} + +/// Converts a Set of Circles into something serializable in JSON. +List> serializeCircleSet(Set circles) { + if (circles == null) { + return null; + } + return circles.map>((Circle p) => p.toJson()).toList(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/marker.dart new file mode 100644 index 000000000000..7a2c76d8055b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/marker.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium 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 '../types.dart'; + +/// Converts an [Iterable] of Markers in a Map of MarkerId -> Marker. +Map keyByMarkerId(Iterable markers) { + if (markers == null) { + return {}; + } + return Map.fromEntries(markers.map((Marker marker) => + MapEntry(marker.markerId, marker.clone()))); +} + +/// Converts a Set of Markers into something serializable in JSON. +List> serializeMarkerSet(Set markers) { + if (markers == null) { + return null; + } + return markers.map>((Marker m) => m.toJson()).toList(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polygon.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polygon.dart new file mode 100644 index 000000000000..9434ddaa077d --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polygon.dart @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium 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 '../types.dart'; + +/// Converts an [Iterable] of Polygons in a Map of PolygonId -> Polygon. +Map keyByPolygonId(Iterable polygons) { + if (polygons == null) { + return {}; + } + return Map.fromEntries(polygons.map((Polygon polygon) => + MapEntry(polygon.polygonId, polygon.clone()))); +} + +/// Converts a Set of Polygons into something serializable in JSON. +List> serializePolygonSet(Set polygons) { + if (polygons == null) { + return null; + } + return polygons.map>((Polygon p) => p.toJson()).toList(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polyline.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polyline.dart new file mode 100644 index 000000000000..9cef6319ddb5 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polyline.dart @@ -0,0 +1,25 @@ +// Copyright 2019 The Chromium 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 '../types.dart'; + +/// Converts an [Iterable] of Polylines in a Map of PolylineId -> Polyline. +Map keyByPolylineId(Iterable polylines) { + if (polylines == null) { + return {}; + } + return Map.fromEntries(polylines.map( + (Polyline polyline) => MapEntry( + polyline.polylineId, polyline.clone()))); +} + +/// Converts a Set of Polylines into something serializable in JSON. +List> serializePolylineSet(Set polylines) { + if (polylines == null) { + return null; + } + return polylines + .map>((Polyline p) => p.toJson()) + .toList(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..2cd840b07e3a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -0,0 +1,23 @@ +name: google_maps_flutter_platform_interface +description: A common platform interface for the google_maps_flutter plugin. +homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 1.0.0 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + plugin_platform_interface: ^1.0.1 + stream_transform: ^1.2.0 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^4.1.1 + pedantic: ^1.8.0 + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart new file mode 100644 index 000000000000..a003b94d544c --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart @@ -0,0 +1,71 @@ +// Copyright 2017 The Chromium 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 'package:mockito/mockito.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$GoogleMapsFlutterPlatform', () { + test('$MethodChannelGoogleMapsFlutter() is the default instance', () { + expect(GoogleMapsFlutterPlatform.instance, + isInstanceOf()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + GoogleMapsFlutterPlatform.instance = + ImplementsGoogleMapsFlutterPlatform(); + }, throwsA(isInstanceOf())); + }); + + test('Can be mocked with `implements`', () { + final GoogleMapsFlutterPlatformMock mock = + GoogleMapsFlutterPlatformMock(); + GoogleMapsFlutterPlatform.instance = mock; + }); + + test('Can be extended', () { + GoogleMapsFlutterPlatform.instance = ExtendsGoogleMapsFlutterPlatform(); + }); + }); + + group('$MethodChannelGoogleMapsFlutter', () { + const MethodChannel channel = + MethodChannel('plugins.flutter.io/google_maps_flutter'); + final List log = []; + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + }); + +// final MethodChannelGoogleMapsFlutter map = MethodChannelGoogleMapsFlutter(0); + + tearDown(() { + log.clear(); + }); + + test('foo', () async { +// await map.foo(); + expect( + log, + [], + ); + }); + }); +} + +class GoogleMapsFlutterPlatformMock extends Mock + with MockPlatformInterfaceMixin + implements GoogleMapsFlutterPlatform {} + +class ImplementsGoogleMapsFlutterPlatform extends Mock + implements GoogleMapsFlutterPlatform {} + +class ExtendsGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform {} diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 8b1d4b320f09..f21a2f84f88f 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -15,6 +15,8 @@ readonly EXCLUDED_PLUGINS_LIST=( "connectivity_platform_interface" "connectivity_web" "flutter_plugin_android_lifecycle" + "google_maps_flutter_platform_interface" + "google_maps_flutter_web" "google_sign_in_platform_interface" "google_sign_in_web" "instrumentation_adapter"