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 index 0ea36280b98..ed666da52c9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.13.0 + +* Adds Advanced marker support. + ## 2.12.1 * Fixes the `zIndex` issue in the `copyWith` method. 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 index f81473aaad3..b5e6457909c 100644 --- 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 @@ -430,6 +430,11 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { return null; } + /// Returns true if [AdvancedMarker]s can be used with this map. + Future isAdvancedMarkersAvailable({required int mapId}) async { + return false; + } + /// Returns a widget displaying the map view. @Deprecated('Use buildViewWithConfiguration instead.') Widget buildView( diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/advanced_marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/advanced_marker.dart new file mode 100644 index 00000000000..71ffd9ad22a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/advanced_marker.dart @@ -0,0 +1,176 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show Offset; +import 'package:flutter/foundation.dart'; + +import '../../google_maps_flutter_platform_interface.dart'; + +/// Marks a geographical location on the map. +/// +/// Extends [Marker] and provides additional features. +@immutable +class AdvancedMarker extends Marker { + /// Creates a set of marker configuration options. + /// + /// Specifies a marker with a given [collisionBehavior]. Default is + /// [MarkerCollisionBehavior.required]. + AdvancedMarker({ + required super.markerId, + super.alpha, + super.anchor, + super.consumeTapEvents, + super.draggable, + super.flat, + super.icon, + super.infoWindow, + super.position, + super.rotation, + super.visible, + super.clusterManagerId, + super.onTap, + super.onDrag, + super.onDragStart, + super.onDragEnd, + int zIndex = 0, + this.collisionBehavior = MarkerCollisionBehavior.requiredDisplay, + }) : super(zIndex: zIndex.toDouble()); + + /// Indicates how the marker behaves when it collides with other markers. + final MarkerCollisionBehavior collisionBehavior; + + /// Creates a new [AdvancedMarker] object whose values are the same as this + /// instance, unless overwritten by the specified parameters. + @override + AdvancedMarker copyWith({ + double? alphaParam, + Offset? anchorParam, + bool? consumeTapEventsParam, + bool? draggableParam, + bool? flatParam, + BitmapDescriptor? iconParam, + InfoWindow? infoWindowParam, + LatLng? positionParam, + double? rotationParam, + bool? visibleParam, + @Deprecated( + 'Use zIndexIntParam instead. ' + 'On some platforms zIndex is truncated to an int, which can lead to incorrect/unstable ordering.', + ) + double? zIndexParam, + int? zIndexIntParam, + VoidCallback? onTapParam, + ValueChanged? onDragStartParam, + ValueChanged? onDragParam, + ValueChanged? onDragEndParam, + ClusterManagerId? clusterManagerIdParam, + MarkerCollisionBehavior? collisionBehaviorParam, + double? altitudeParam, + }) { + return AdvancedMarker( + 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: (zIndexIntParam ?? zIndexParam ?? zIndex).toInt(), + onTap: onTapParam ?? onTap, + onDragStart: onDragStartParam ?? onDragStart, + onDrag: onDragParam ?? onDrag, + onDragEnd: onDragEndParam ?? onDragEnd, + clusterManagerId: clusterManagerIdParam ?? clusterManagerId, + collisionBehavior: collisionBehaviorParam ?? collisionBehavior, + ); + } + + /// Converts this object to something serializable in JSON. + @override + Object toJson() { + final String? clusterManagerIdValue = clusterManagerId?.value; + + return { + 'markerId': markerId.value, + 'alpha': alpha, + 'consumeTapEvents': consumeTapEvents, + 'draggable': draggable, + 'flat': flat, + 'icon': icon.toJson(), + 'infoWindow': infoWindow.toJson(), + 'position': position.toJson(), + 'rotation': rotation, + 'visible': visible, + 'zIndex': zIndex, + 'collisionBehavior': collisionBehavior.index, + 'anchor': _offsetToJson(anchor), + if (clusterManagerIdValue != null) + 'clusterManagerId': clusterManagerIdValue, + }; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is AdvancedMarker && + markerId == other.markerId && + alpha == other.alpha && + anchor == other.anchor && + consumeTapEvents == other.consumeTapEvents && + draggable == other.draggable && + flat == other.flat && + icon == other.icon && + infoWindow == other.infoWindow && + position == other.position && + rotation == other.rotation && + visible == other.visible && + zIndex == other.zIndex && + clusterManagerId == other.clusterManagerId && + collisionBehavior == other.collisionBehavior; + } + + @override + int get hashCode => markerId.hashCode; + + @override + String toString() { + return 'AdvancedMarker{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, onDragStart: $onDragStart, ' + 'onDrag: $onDrag, onDragEnd: $onDragEnd, clusterManagerId: $clusterManagerId, ' + 'collisionBehavior: $collisionBehavior}'; + } +} + +/// Indicates how the marker behaves when it collides with other markers. +enum MarkerCollisionBehavior { + /// (default) Always display the marker regardless of collision. + requiredDisplay, + + /// Display the marker only if it does not overlap with other markers. + /// If two markers of this type would overlap, the one with the higher zIndex + /// is shown. If they have the same zIndex, the one with the lower vertical + /// screen position is shown. + optionalAndHidesLowerPriority, + + /// Always display the marker regardless of collision, and hide any + /// [optionalAndHidesLowerPriority] markers or labels that would overlap with + /// the marker. + requiredAndHidesOptional, +} + +/// Convert [Offset] to JSON object. +Object _offsetToJson(Offset offset) { + return [offset.dx, offset.dy]; +} 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 index e25d3c7a2c5..0598b2c570f 100644 --- 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 @@ -4,6 +4,7 @@ import 'dart:async' show Future; import 'dart:typed_data' show Uint8List; +import 'dart:ui'; import 'package:flutter/foundation.dart' show kIsWeb, visibleForTesting; import 'package:flutter/material.dart' @@ -52,6 +53,8 @@ const double _naturalPixelRatio = 1.0; /// a default marker icon. /// Use the [BitmapDescriptor.defaultMarkerWithHue] to create a /// [BitmapDescriptor] for a default marker icon with a hue value. +/// Use the [BitmapDescriptor.pinConfig] to create a custom icon for +/// [AdvancedMarker]. abstract class BitmapDescriptor { const BitmapDescriptor._(); @@ -208,6 +211,9 @@ abstract class BitmapDescriptor { /// 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]. + /// + /// Doesn't work with [AdvancedMarker]s, [BitmapDescriptor.pinConfig] should + /// be used instead. static BitmapDescriptor defaultMarkerWithHue(double hue) { assert(0.0 <= hue && hue < 360.0); return DefaultMarker(hue: hue); @@ -330,6 +336,28 @@ abstract class BitmapDescriptor { ); } + /// Creates a [BitmapDescriptor] that can be used to customize + /// [AdvancedMarker]'s pin. + /// + /// [backgroundColor] is the color of the pin's background. + /// [borderColor] is the color of the pin's border. + /// [glyph] is the pin's glyph to be displayed on the pin. + /// + /// See [PinConfig] for more information on the parameters. + /// + /// Returns a new [PinConfig] instance. + static BitmapDescriptor pinConfig({ + Color? backgroundColor, + Color? borderColor, + AdvancedMarkerGlyph? glyph, + }) { + return PinConfig( + backgroundColor: backgroundColor, + borderColor: borderColor, + glyph: glyph, + ); + } + /// Convert the object to a Json format. Object toJson(); } @@ -954,3 +982,173 @@ class BytesMapBitmap extends MapBitmap { } ]; } + +/// Represents a [BitmapDescriptor] that is created from a pin configuration. +/// Can only be used with [AdvancedMarker]s. +/// +/// The [backgroundColor] and [borderColor] are used to define the color of the +/// standard pin marker. +/// +/// The [glyph] parameter is used to define the glyph that is displayed on the +/// pin marker (default glyph is a circle). +/// +/// The following example demonstrates how to change colors of the default map +/// pin to white and blue: +/// +/// ```dart +/// PinConfig( +/// backgroundColor: Colors.blue, +/// borderColor: Colors.white, +/// glyph: CircleGlyph(color: Colors.blue) +/// ) +/// ``` +/// +/// The following example demonstrates how to customize a marker pin by showing +/// a short text on the pin: +/// +/// ```dart +/// PinConfig( +/// backgroundColor: Colors.blue, +/// glyph: TextGlyph(text: 'Pin', textColor: Colors.white) +/// ) +/// ``` +/// +/// The following example demonstrates how to customize a marker pin by showing +/// a custom image on the pin: +/// +/// ```dart +/// PinConfig( +/// glyph: BitmapGlyph( +/// bitmap: BitmapDescriptor.asset( +/// ImageConfiguration(size: Size(12, 12)), +/// 'assets/cat.png' +/// ) +/// ) +/// ``` +/// +class PinConfig extends BitmapDescriptor { + /// Constructs a [PinConfig] that is created from a pin configuration. + /// + /// The [backgroundColor] and [borderColor] are used to define the color of + /// the standard pin marker. + /// + /// The [glyph] parameter is used to define the glyph that is displayed on the + /// pin marker. + /// + /// At least one of the parameters must not be null. + const PinConfig({ + this.backgroundColor, + this.borderColor, + this.glyph, + }) : assert( + backgroundColor != null || borderColor != null || glyph != null, + 'Cannot create PinConfig with all parameters being null.', + ), + super._(); + + /// The type of the MapBitmap object, used for the JSON serialization. + static const String type = 'pinConfig'; + + /// The background color of the pin. + final Color? backgroundColor; + + /// The border color of the pin. + final Color? borderColor; + + /// The glyph that is displayed on the pin marker. If null, the default + /// circular glyph is used. + /// + /// Can be one of the following: + /// * [CircleGlyph] to define a circular glyph with a custom color. + /// * [BitmapGlyph] to define a glyph with a specified bitmap. + /// * [TextGlyph] to define a glyph with a specified text and its color. + final AdvancedMarkerGlyph? glyph; + + @override + Object toJson() => [ + type, + { + if (backgroundColor != null) + 'backgroundColor': backgroundColor?.value, + if (borderColor != null) 'borderColor': borderColor?.value, + if (glyph != null) 'glyph': glyph?.toJson(), + } + ]; +} + +/// Defines a glyph (the element at the center of an [AdvancedMarker] icon). +sealed class AdvancedMarkerGlyph extends BitmapDescriptor { + const AdvancedMarkerGlyph._() : super._(); +} + +/// Defines a circular glyph with a given color. +class CircleGlyph extends AdvancedMarkerGlyph { + /// Constructs a glyph instance, using the default circle, but with + /// a custom color. + const CircleGlyph({ + required this.color, + }) : super._(); + + /// Color of the circular icon. + final Color color; + + @override + Object toJson() => [ + 'circleGlyph', + { + 'color': color.value, + } + ]; +} + +/// Defines a glyph instance with a specified bitmap. +class BitmapGlyph extends AdvancedMarkerGlyph { + /// Constructs a glyph with the specified [bitmap]. + /// + /// [bitmap] is the image to be displayed in the center of the glyph. Must not + /// be an [AdvancedMarkerGlyph]. + const BitmapGlyph({ + required this.bitmap, + }) : assert( + bitmap is! AdvancedMarkerGlyph, + 'BitmapDescriptor cannot be an AdvancedMarkerGlyph.', + ), + super._(); + + /// Bitmap image to be displayed in the center of the glyph. + final BitmapDescriptor bitmap; + + @override + Object toJson() => [ + 'bitmapGlyph', + { + 'bitmap': bitmap.toJson(), + } + ]; +} + +/// Defines a glyph instance with a specified text and color. +class TextGlyph extends AdvancedMarkerGlyph { + /// Constructs a glyph with the specified [text] and [textColor]. + const TextGlyph({ + required this.text, + this.textColor, + }) : super._(); + + /// Text to be displayed in the glyph. + final String text; + + /// Color of the text. + final Color? textColor; + + @override + Object toJson() { + return [ + 'textGlyph', + { + 'text': text, + if (textColor != null) 'textColor': textColor!.value, + } + ]; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart index 6abd6c641e8..5162cd50813 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart @@ -35,9 +35,12 @@ class MapConfiguration { this.indoorViewEnabled, this.trafficEnabled, this.buildingsEnabled, - this.cloudMapId, + String? mapId, + @Deprecated('cloudMapId is deprecated. Use mapId instead.') + String? cloudMapId, this.style, - }); + this.markerType, + }) : mapId = mapId ?? cloudMapId; /// This setting controls how the API handles gestures on the map. Web only. /// @@ -112,13 +115,29 @@ class MapConfiguration { /// /// See https://developers.google.com/maps/documentation/get-map-id /// for more details. - final String? cloudMapId; + final String? mapId; /// Locally configured JSON style. /// /// To clear a previously set style, set this to an empty string. final String? style; + /// The type of marker that the map should use. + /// + /// Advanced and legacy markers could be handled differently by platform + /// implementations. This property indicates which type of marker should be + /// used. + final MarkerType? markerType; + + /// Identifier that's associated with a specific cloud-based map style. + /// + /// See https://developers.google.com/maps/documentation/get-map-id + /// for more details. + /// + /// Deprecated in favor of [mapId]. + @Deprecated('cloudMapId is deprecated. Use mapId instead.') + String? get cloudMapId => mapId; + /// Returns a new options object containing only the values of this instance /// that are different from [other]. MapConfiguration diffFrom(MapConfiguration other) { @@ -179,8 +198,9 @@ class MapConfiguration { trafficEnabled != other.trafficEnabled ? trafficEnabled : null, buildingsEnabled: buildingsEnabled != other.buildingsEnabled ? buildingsEnabled : null, - cloudMapId: cloudMapId != other.cloudMapId ? cloudMapId : null, + mapId: mapId != other.mapId ? mapId : null, style: style != other.style ? style : null, + markerType: markerType != other.markerType ? markerType : null, ); } @@ -212,8 +232,9 @@ class MapConfiguration { indoorViewEnabled: diff.indoorViewEnabled ?? indoorViewEnabled, trafficEnabled: diff.trafficEnabled ?? trafficEnabled, buildingsEnabled: diff.buildingsEnabled ?? buildingsEnabled, - cloudMapId: diff.cloudMapId ?? cloudMapId, + mapId: diff.mapId ?? mapId, style: diff.style ?? style, + markerType: diff.markerType ?? markerType, ); } @@ -239,8 +260,9 @@ class MapConfiguration { indoorViewEnabled == null && trafficEnabled == null && buildingsEnabled == null && - cloudMapId == null && - style == null; + mapId == null && + style == null && + markerType == null; @override bool operator ==(Object other) { @@ -271,8 +293,9 @@ class MapConfiguration { indoorViewEnabled == other.indoorViewEnabled && trafficEnabled == other.trafficEnabled && buildingsEnabled == other.buildingsEnabled && - cloudMapId == other.cloudMapId && - style == other.style; + mapId == other.mapId && + style == other.style && + markerType == other.markerType; } @override @@ -297,7 +320,18 @@ class MapConfiguration { indoorViewEnabled, trafficEnabled, buildingsEnabled, - cloudMapId, + mapId, style, + markerType, ]); } + +/// Indicates the type of marker that the map should use. +enum MarkerType { + /// Represents the default marker type, [Marker]. This marker type is + /// deprecated on the web. + marker, + + /// Represents the advanced marker type, [AdvancedMarker]. + advancedMarker, +} 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 index d0c1f036c31..d0093f3716f 100644 --- 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 @@ -64,7 +64,8 @@ class InfoWindow { ); } - Object _toJson() { + /// Converts this object to something serializable in JSON. + Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { @@ -117,6 +118,8 @@ class MarkerId extends MapsObjectId { /// 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. +/// +/// Deprecated on the web in favor of [AdvancedMarker]. @immutable class Marker implements MapsObject { /// Creates a set of marker configuration options. @@ -325,7 +328,7 @@ class Marker implements MapsObject { addIfPresent('draggable', draggable); addIfPresent('flat', flat); addIfPresent('icon', icon.toJson()); - addIfPresent('infoWindow', infoWindow._toJson()); + addIfPresent('infoWindow', infoWindow.toJson()); addIfPresent('position', position.toJson()); addIfPresent('rotation', rotation); addIfPresent('visible', visible); 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 index 95c27d5bf95..08c34d29ea7 100644 --- 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 @@ -3,6 +3,7 @@ // found in the LICENSE file. // All the public types exposed by this package. +export 'advanced_marker.dart'; export 'bitmap.dart'; export 'callbacks.dart'; export 'camera.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart index 43b25fa642d..bba79dfcc37 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart @@ -59,6 +59,8 @@ Map jsonForMapConfiguration(MapConfiguration config) { if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!, + if (config.mapId != null) 'mapId': config.mapId!, if (config.style != null) 'style': config.style!, + if (config.markerType != null) 'markerType': config.markerType!.index, }; } 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 index 3e8d1b950e8..81c20b09a8a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # 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: 2.12.1 +version: 2.13.0 environment: sdk: ^3.6.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 index faccbab1d66..6d8583e82a0 100644 --- 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 @@ -122,6 +122,15 @@ void main() { }, ); + test( + 'default implementation of isAdvancedMarkersAvailable returns false', + () async { + final GoogleMapsFlutterPlatform platform = + BuildViewGoogleMapsFlutterPlatform(); + expect(await platform.isAdvancedMarkersAvailable(mapId: 0), isFalse); + }, + ); + test( 'default implementation of `animateCameraWithConfiguration` delegates to `animateCamera`', () { @@ -170,6 +179,7 @@ class BuildViewGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, + MarkerType markerType = MarkerType.marker, }) { return const Text(''); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/advanced_marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/advanced_marker_test.dart new file mode 100644 index 00000000000..1ddc5c947e6 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/advanced_marker_test.dart @@ -0,0 +1,214 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$AdvancedMarker', () { + test('constructor defaults', () { + final AdvancedMarker marker = + AdvancedMarker(markerId: const MarkerId('ABC123')); + + expect(marker.alpha, equals(1.0)); + expect(marker.anchor, equals(const Offset(0.5, 1.0))); + expect(marker.consumeTapEvents, equals(false)); + expect(marker.draggable, equals(false)); + expect(marker.flat, equals(false)); + expect(marker.icon, equals(BitmapDescriptor.defaultMarker)); + expect(marker.infoWindow, equals(InfoWindow.noText)); + expect(marker.position, equals(const LatLng(0.0, 0.0))); + expect(marker.rotation, equals(0.0)); + expect(marker.visible, equals(true)); + expect(marker.zIndex, equals(0.0)); + expect(marker.onTap, equals(null)); + expect(marker.onDrag, equals(null)); + expect(marker.onDragStart, equals(null)); + expect(marker.onDragEnd, equals(null)); + expect(marker.collisionBehavior, MarkerCollisionBehavior.requiredDisplay); + }); + + test('constructor alpha is >= 0.0 and <= 1.0', () { + void initWithAlpha(double alpha) { + AdvancedMarker(markerId: const MarkerId('ABC123'), alpha: alpha); + } + + expect(() => initWithAlpha(-0.5), throwsAssertionError); + expect(() => initWithAlpha(0.0), isNot(throwsAssertionError)); + expect(() => initWithAlpha(0.5), isNot(throwsAssertionError)); + expect(() => initWithAlpha(1.0), isNot(throwsAssertionError)); + expect(() => initWithAlpha(100), throwsAssertionError); + }); + + test('toJson', () { + final BitmapDescriptor testDescriptor = + BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); + final AdvancedMarker marker = AdvancedMarker( + markerId: const MarkerId('ABC123'), + alpha: 0.12345, + anchor: const Offset(100, 100), + consumeTapEvents: true, + draggable: true, + flat: true, + icon: testDescriptor, + infoWindow: const InfoWindow( + title: 'Test title', + snippet: 'Test snippet', + anchor: Offset(100, 200), + ), + position: const LatLng(50, 50), + rotation: 100, + visible: false, + zIndex: 100, + onTap: () {}, + onDragStart: (LatLng latLng) {}, + onDrag: (LatLng latLng) {}, + onDragEnd: (LatLng latLng) {}, + collisionBehavior: MarkerCollisionBehavior.requiredAndHidesOptional, + ); + + final Map json = marker.toJson() as Map; + + expect(json, { + 'markerId': 'ABC123', + 'alpha': 0.12345, + 'anchor': [100, 100], + 'consumeTapEvents': true, + 'draggable': true, + 'flat': true, + 'icon': testDescriptor.toJson(), + 'infoWindow': { + 'title': 'Test title', + 'snippet': 'Test snippet', + 'anchor': [100.0, 200.0], + }, + 'position': [50, 50], + 'rotation': 100.0, + 'visible': false, + 'zIndex': 100.0, + 'collisionBehavior': + MarkerCollisionBehavior.requiredAndHidesOptional.index, + }); + }); + + test('clone', () { + final Marker marker = AdvancedMarker(markerId: const MarkerId('ABC123')); + final Marker clone = marker.clone(); + + expect(clone, isA()); + expect(identical(clone, marker), isFalse); + expect(clone, equals(marker)); + }); + + test('copyWith', () { + const MarkerId markerId = MarkerId('ABC123'); + final AdvancedMarker marker = AdvancedMarker(markerId: markerId); + + final BitmapDescriptor testDescriptor = + BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); + const double testAlphaParam = 0.12345; + const Offset testAnchorParam = Offset(100, 100); + final bool testConsumeTapEventsParam = !marker.consumeTapEvents; + final bool testDraggableParam = !marker.draggable; + final bool testFlatParam = !marker.flat; + final BitmapDescriptor testIconParam = testDescriptor; + const InfoWindow testInfoWindowParam = InfoWindow(title: 'Test'); + const LatLng testPositionParam = LatLng(100, 100); + const double testRotationParam = 100; + final bool testVisibleParam = !marker.visible; + const double testZIndexParam = 100; + const ClusterManagerId testClusterManagerIdParam = + ClusterManagerId('DEF123'); + final List log = []; + const MarkerCollisionBehavior testCollisionBehavior = + MarkerCollisionBehavior.requiredAndHidesOptional; + + final AdvancedMarker copy = marker.copyWith( + alphaParam: testAlphaParam, + anchorParam: testAnchorParam, + consumeTapEventsParam: testConsumeTapEventsParam, + draggableParam: testDraggableParam, + flatParam: testFlatParam, + iconParam: testIconParam, + infoWindowParam: testInfoWindowParam, + positionParam: testPositionParam, + rotationParam: testRotationParam, + visibleParam: testVisibleParam, + zIndexParam: testZIndexParam, + clusterManagerIdParam: testClusterManagerIdParam, + collisionBehaviorParam: testCollisionBehavior, + onTapParam: () { + log.add('onTapParam'); + }, + onDragStartParam: (LatLng latLng) { + log.add('onDragStartParam'); + }, + onDragParam: (LatLng latLng) { + log.add('onDragParam'); + }, + onDragEndParam: (LatLng latLng) { + log.add('onDragEndParam'); + }, + ); + + expect(copy.markerId, equals(markerId)); + expect(copy.alpha, equals(testAlphaParam)); + expect(copy.anchor, equals(testAnchorParam)); + expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam)); + expect(copy.draggable, equals(testDraggableParam)); + expect(copy.flat, equals(testFlatParam)); + expect(copy.icon, equals(testIconParam)); + expect(copy.infoWindow, equals(testInfoWindowParam)); + expect(copy.position, equals(testPositionParam)); + expect(copy.rotation, equals(testRotationParam)); + expect(copy.visible, equals(testVisibleParam)); + expect(copy.zIndex, equals(testZIndexParam)); + expect(copy.clusterManagerId, equals(testClusterManagerIdParam)); + expect(copy.collisionBehavior, equals(testCollisionBehavior)); + + copy.onTap!(); + expect(log, contains('onTapParam')); + + copy.onDragStart!(const LatLng(0, 1)); + expect(log, contains('onDragStartParam')); + + copy.onDrag!(const LatLng(0, 1)); + expect(log, contains('onDragParam')); + + copy.onDragEnd!(const LatLng(0, 1)); + expect(log, contains('onDragEndParam')); + }); + + test('zIndex param', () { + final AdvancedMarker marker = AdvancedMarker( + markerId: const MarkerId('ABC123'), + zIndex: 5, + ); + + expect(marker.zIndexInt, 5); + expect(marker.zIndex, 5.00); + }); + + test('zIndexInt param copyWith', () { + final AdvancedMarker marker = AdvancedMarker( + markerId: const MarkerId('ABC123'), + zIndex: 5, + ); + final AdvancedMarker copy = marker.copyWith(zIndexIntParam: 10); + expect(copy.zIndexInt, 10); + expect(copy.zIndex, 10.0); + }); + + test('zIndex param copyWith', () { + final AdvancedMarker marker = AdvancedMarker( + markerId: const MarkerId('ABC123'), + zIndex: 5, + ); + final AdvancedMarker copy = marker.copyWith(zIndexParam: 10.0); + expect(copy.zIndexInt, 10); + expect(copy.zIndex, 10.0); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart index 001dbe755e3..44e63badbbd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart @@ -682,6 +682,91 @@ void main() { }); }); + group('PinConfig', () { + test('construct with empty values, throws assertion error', () { + expect(() => PinConfig(), throwsAssertionError); + }); + + test('construct', () { + const PinConfig pinConfig = PinConfig( + backgroundColor: Colors.green, + borderColor: Colors.blue, + ); + expect(pinConfig, isA()); + expect(pinConfig.backgroundColor, Colors.green); + expect(pinConfig.borderColor, Colors.blue); + expect( + pinConfig.toJson(), + [ + PinConfig.type, + { + 'backgroundColor': Colors.green.value, + 'borderColor': Colors.blue.value, + }, + ], + ); + }); + + test('construct with glyph text', () { + const PinConfig pinConfig = PinConfig( + backgroundColor: Colors.green, + borderColor: Colors.blue, + glyph: TextGlyph(text: 'Hello', textColor: Colors.red), + ); + expect(pinConfig.glyph, isA()); + expect((pinConfig.glyph! as TextGlyph).text, 'Hello'); + expect((pinConfig.glyph! as TextGlyph).textColor, Colors.red); + expect( + pinConfig.toJson(), + [ + PinConfig.type, + { + 'backgroundColor': Colors.green.value, + 'borderColor': Colors.blue.value, + 'glyph': [ + 'textGlyph', + { + 'text': 'Hello', + 'textColor': Colors.red.value, + } + ], + }, + ], + ); + }); + + test('construct with glyph bitmap', () async { + const BitmapDescriptor bitmap = AssetBitmap(name: 'red_square.png'); + const PinConfig pinConfig = PinConfig( + backgroundColor: Colors.black, + borderColor: Colors.red, + glyph: BitmapGlyph(bitmap: bitmap), + ); + + expect(pinConfig.backgroundColor, Colors.black); + expect(pinConfig.borderColor, Colors.red); + expect( + pinConfig.toJson(), + [ + PinConfig.type, + { + 'glyph': [ + 'bitmapGlyph', + { + 'bitmap': [ + 'fromAsset', + 'red_square.png', + ], + }, + ], + 'backgroundColor': Colors.black.value, + 'borderColor': Colors.red.value, + }, + ], + ); + }); + }); + test('mapBitmapScaling from String', () { expect(mapBitmapScalingFromString('auto'), MapBitmapScaling.auto); expect(mapBitmapScalingFromString('none'), MapBitmapScaling.none); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart index e34f32676e0..bcd69a0e82f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; -const String _kCloudMapId = '000000000000000'; // Dummy map ID. +const String _kMapId = '000000000000000'; // Dummy map ID. void main() { group('diffs', () { @@ -58,7 +58,7 @@ void main() { expect(updated.liteModeEnabled, isNot(null)); expect(updated.padding, isNot(null)); expect(updated.trafficEnabled, isNot(null)); - expect(updated.cloudMapId, null); + expect(updated.mapId, null); }); test('handle webGestureHandling', () async { @@ -395,7 +395,7 @@ void main() { }); test('handle cloudMapId', () async { - const MapConfiguration diff = MapConfiguration(cloudMapId: _kCloudMapId); + const MapConfiguration diff = MapConfiguration(cloudMapId: _kMapId); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); @@ -405,7 +405,24 @@ void main() { // The diff from empty options should be the diff itself. expect(diff.diffFrom(empty), diff); // A diff applied to non-empty options should update that field. - expect(updated.cloudMapId, _kCloudMapId); + expect(updated.cloudMapId, _kMapId); + expect(updated.mapId, _kMapId); + // The hash code should change. + expect(empty.hashCode, isNot(diff.hashCode)); + }); + + test('handle mapId', () async { + const MapConfiguration diff = MapConfiguration(mapId: _kMapId); + + const MapConfiguration empty = MapConfiguration(); + final MapConfiguration updated = diffBase.applyDiff(diff); + + // A diff applied to empty options should be the diff itself. + expect(empty.applyDiff(diff), diff); + // The diff from empty options should be the diff itself. + expect(diff.diffFrom(empty), diff); + // A diff applied to non-empty options should update that field. + expect(updated.mapId, _kMapId); // The hash code should change. expect(empty.hashCode, isNot(diff.hashCode)); }); @@ -555,7 +572,13 @@ void main() { }); test('is false with cloudMapId', () async { - const MapConfiguration diff = MapConfiguration(cloudMapId: _kCloudMapId); + const MapConfiguration diff = MapConfiguration(mapId: _kMapId); + + expect(diff.isEmpty, false); + }); + + test('is false with mapId', () async { + const MapConfiguration diff = MapConfiguration(mapId: _kMapId); expect(diff.isEmpty, false); }); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/map_configuration_serialization_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/map_configuration_serialization_test.dart index 80618356c1c..3d00e04495b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/map_configuration_serialization_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/map_configuration_serialization_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_platform_interface/src/types/utils/map_configuration_serialization.dart'; -const String _kCloudMapId = '000000000000000'; // Dummy map ID. +const String _kMapId = '000000000000000'; // Dummy map ID. void main() { test('empty serialization', () async { @@ -20,26 +20,28 @@ void main() { test('complete serialization', () async { final MapConfiguration config = MapConfiguration( - compassEnabled: false, - mapToolbarEnabled: false, - cameraTargetBounds: CameraTargetBounds(LatLngBounds( - northeast: const LatLng(30, 20), southwest: const LatLng(10, 40))), - mapType: MapType.normal, - minMaxZoomPreference: const MinMaxZoomPreference(1.0, 10.0), - rotateGesturesEnabled: false, - scrollGesturesEnabled: false, - tiltGesturesEnabled: false, - trackCameraPosition: false, - zoomControlsEnabled: false, - zoomGesturesEnabled: false, - liteModeEnabled: false, - myLocationEnabled: false, - myLocationButtonEnabled: false, - padding: const EdgeInsets.all(5.0), - indoorViewEnabled: false, - trafficEnabled: false, - buildingsEnabled: false, - cloudMapId: _kCloudMapId); + compassEnabled: false, + mapToolbarEnabled: false, + cameraTargetBounds: CameraTargetBounds(LatLngBounds( + northeast: const LatLng(30, 20), southwest: const LatLng(10, 40))), + mapType: MapType.normal, + minMaxZoomPreference: const MinMaxZoomPreference(1.0, 10.0), + rotateGesturesEnabled: false, + scrollGesturesEnabled: false, + tiltGesturesEnabled: false, + trackCameraPosition: false, + zoomControlsEnabled: false, + zoomGesturesEnabled: false, + liteModeEnabled: false, + myLocationEnabled: false, + myLocationButtonEnabled: false, + padding: const EdgeInsets.all(5.0), + indoorViewEnabled: false, + trafficEnabled: false, + buildingsEnabled: false, + mapId: _kMapId, + cloudMapId: _kMapId, + ); final Map json = jsonForMapConfiguration(config); @@ -72,7 +74,31 @@ void main() { 'indoorEnabled': false, 'trafficEnabled': false, 'buildingsEnabled': false, - 'cloudMapId': _kCloudMapId + 'mapId': _kMapId, + 'cloudMapId': _kMapId, + }); + }); + + test('mapId preferred over cloudMapId', () { + const MapConfiguration config = MapConfiguration( + mapId: 'map-id', + cloudMapId: 'cloud-map-id', + ); + final Map json = jsonForMapConfiguration(config); + expect(json, { + 'mapId': 'map-id', + 'cloudMapId': 'map-id', + }); + }); + + test('mapId falls back to cloudMapId', () { + const MapConfiguration config = MapConfiguration( + cloudMapId: 'cloud-map-id', + ); + final Map json = jsonForMapConfiguration(config); + expect(json, { + 'mapId': 'cloud-map-id', + 'cloudMapId': 'cloud-map-id', }); }); }