From b3f10aaa157eee76a9248df21a8899b0b2461e19 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 29 Feb 2024 21:36:03 +0200 Subject: [PATCH] [google_maps_flutter] Add support for marker clustering - platform interface (#6158) This PR introduces support for marker clustering This is prequel PR for: https://github.com/flutter/packages/pull/4319 Containing only changes to `google_maps_flutter_platform_interface` package. Follow up PR:s will hold the platform and app-facing plugin implementations. Linked issue: https://github.com/flutter/flutter/issues/26863 --- .../AUTHORS | 1 + .../CHANGELOG.md | 4 + .../lib/src/events/map_event.dart | 9 ++ .../method_channel_google_maps_flutter.dart | 50 +++++++++++ .../google_maps_flutter_platform.dart | 19 +++++ .../google_maps_inspector_platform.dart | 6 ++ .../lib/src/types/cluster.dart | 52 ++++++++++++ .../lib/src/types/cluster_manager.dart | 82 +++++++++++++++++++ .../src/types/cluster_manager_updates.dart | 25 ++++++ .../lib/src/types/map_objects.dart | 2 + .../lib/src/types/marker.dart | 12 ++- .../lib/src/types/types.dart | 4 + .../lib/src/types/utils/cluster_manager.dart | 13 +++ .../pubspec.yaml | 2 +- .../google_maps_flutter_platform_test.dart | 30 +++++++ .../test/types/cluster_manager_test.dart | 57 +++++++++++++ .../test/types/cluster_test.dart | 53 ++++++++++++ .../test/types/marker_test.dart | 8 +- .../test/utils/cluster_manager_test.dart | 30 +++++++ 19 files changed, 455 insertions(+), 4 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS index 493a0b4ef9c2..6f33f4aa0511 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +Joonas Kerttula 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 e062daa45430..668dcb2731b3 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.6.0 + +* Adds support for marker clustering. + ## 2.5.0 * Adds `style` to the `MapConfiguration` to allow setting style as part of 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 index 0034633b8066..67a026d90557 100644 --- 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 @@ -167,3 +167,12 @@ class MapLongPressEvent extends _PositionedMapEvent { /// The `position` of this event is the LatLng where the Map was long pressed. MapLongPressEvent(int mapId, LatLng position) : super(mapId, position, null); } + +/// An event fired when a cluster icon managed by [ClusterManager] is tapped. +class ClusterTapEvent extends MapEvent { + /// Build a ClusterTapEvent Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [Cluster] object that represents the tapped + /// cluster icon managed by [ClusterManager]. + ClusterTapEvent(super.mapId, super.cluster); +} 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 index 2c79c0cf995c..dd728cf1349d 100644 --- 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 @@ -166,6 +166,11 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onClusterTap({required int mapId}) { + return _events(mapId).whereType(); + } + Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': @@ -258,6 +263,36 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { arguments['zoom'] as int?, ); return tile.toJson(); + case 'cluster#onTap': + final Map arguments = _getArgumentDictionary(call); + final ClusterManagerId clusterManagerId = + ClusterManagerId(arguments['clusterManagerId']! as String); + final LatLng position = LatLng.fromJson(arguments['position'])!; + + final Map> latLngData = + (arguments['bounds']! as Map).map( + (dynamic key, dynamic object) => + MapEntry>( + key as String, object as List)); + + final LatLngBounds bounds = LatLngBounds( + northeast: LatLng.fromJson(latLngData['northeast'])!, + southwest: LatLng.fromJson(latLngData['southwest'])!); + + final List markerIds = + (arguments['markerIds']! as List) + .map((dynamic markerId) => MarkerId(markerId as String)) + .toList(); + + _mapEventStreamController.add(ClusterTapEvent( + mapId, + Cluster( + clusterManagerId, + markerIds, + position: position, + bounds: bounds, + ), + )); default: throw MissingPluginException(); } @@ -347,6 +382,17 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { ); } + @override + Future updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'clusterManagers#update', + clusterManagerUpdates.toJson(), + ); + } + @override Future clearTileCache( TileOverlayId tileOverlayId, { @@ -585,6 +631,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -599,6 +646,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { polygons: polygons, polylines: polylines, circles: circles, + clusterManagers: clusterManagers, tileOverlays: tileOverlays), mapOptions: mapOptions, ); @@ -614,6 +662,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { @@ -627,6 +676,7 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { polylines: polylines, circles: circles, tileOverlays: tileOverlays, + clusterManagers: clusterManagers, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); 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 9e829c496aa4..648c6a162d68 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 @@ -142,6 +142,20 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('updateTileOverlays() has not been implemented.'); } + /// Updates cluster manager 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 updateClusterManagers( + ClusterManagerUpdates clusterManagerUpdates, { + required int mapId, + }) { + throw UnimplementedError( + 'updateClusterManagers() has not been implemented.'); + } + /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. /// @@ -357,6 +371,11 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('onLongPress() has not been implemented.'); } + /// A marker icon managed by [ClusterManager] has been tapped. + Stream onClusterTap({required int mapId}) { + throw UnimplementedError('onClusterTap() has not been implemented.'); + } + /// Dispose of whatever resources the `mapId` is holding on to. void dispose({required int mapId}) { throw UnimplementedError('dispose() has not been implemented.'); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart index 1e07b97c300d..461d38a61616 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart @@ -115,4 +115,10 @@ abstract class GoogleMapsInspectorPlatform extends PlatformInterface { {required int mapId}) { throw UnimplementedError('getTileOverlayInfo() has not been implemented.'); } + + /// Returns current clusters from [ClusterManager]. + Future> getClusters( + {required int mapId, required ClusterManagerId clusterManagerId}) { + throw UnimplementedError('getClusters() has not been implemented.'); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart new file mode 100644 index 000000000000..d5ca46a72f55 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster.dart @@ -0,0 +1,52 @@ +// 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/foundation.dart' + show immutable, listEquals, objectRuntimeType; +import 'types.dart'; + +/// A cluster containing multiple markers. +@immutable +class Cluster { + /// Creates a cluster with its location [LatLng], bounds [LatLngBounds], + /// and list of [MarkerId]s in the cluster. + const Cluster( + this.clusterManagerId, + this.markerIds, { + required this.position, + required this.bounds, + }) : assert(markerIds.length > 0); + + /// ID of the [ClusterManager] of the cluster. + final ClusterManagerId clusterManagerId; + + /// Cluster marker location. + final LatLng position; + + /// The bounds containing all cluster markers. + final LatLngBounds bounds; + + /// List of [MarkerId]s in the cluster. + final List markerIds; + + /// Returns the number of markers in the cluster. + int get count => markerIds.length; + + @override + String toString() => + '${objectRuntimeType(this, 'Cluster')}($clusterManagerId, $position, $bounds, $markerIds)'; + + @override + bool operator ==(Object other) { + return other is Cluster && + other.clusterManagerId == clusterManagerId && + other.position == position && + other.bounds == bounds && + listEquals(other.markerIds, markerIds); + } + + @override + int get hashCode => + Object.hash(clusterManagerId, position, bounds, markerIds); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart new file mode 100644 index 000000000000..ec3c1f7e435b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager.dart @@ -0,0 +1,82 @@ +// 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/foundation.dart' show immutable; +import 'types.dart'; + +/// Uniquely identifies a [ClusterManager] among [GoogleMap] clusters. +/// +/// This does not have to be globally unique, only unique among the list. +@immutable +class ClusterManagerId extends MapsObjectId { + /// Creates an immutable identifier for a [ClusterManager]. + const ClusterManagerId(super.value); +} + +/// [ClusterManager] manages marker clustering for set of [Marker]s that have +/// the same [ClusterManagerId] set. +@immutable +class ClusterManager implements MapsObject { + /// Creates an immutable object for managing clustering for set of markers. + const ClusterManager({ + required this.clusterManagerId, + this.onClusterTap, + }); + + /// Uniquely identifies a [ClusterManager]. + final ClusterManagerId clusterManagerId; + + @override + ClusterManagerId get mapsId => clusterManagerId; + + /// Callback to receive tap events for cluster markers placed on this map. + final ArgumentCallback? onClusterTap; + + /// Creates a new [ClusterManager] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + ClusterManager copyWith({ + ArgumentCallback? onClusterTapParam, + }) { + return ClusterManager( + clusterManagerId: clusterManagerId, + onClusterTap: onClusterTapParam ?? onClusterTap, + ); + } + + /// Creates a new [ClusterManager] object whose values are the same as this instance. + @override + ClusterManager clone() => copyWith(); + + /// Converts this object to something serializable in JSON. + @override + Object toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, Object? value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('clusterManagerId', clusterManagerId.value); + return json; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is ClusterManager && + clusterManagerId == other.clusterManagerId; + } + + @override + int get hashCode => clusterManagerId.hashCode; + + @override + String toString() { + return 'Cluster{clusterManagerId: $clusterManagerId, onClusterTap: $onClusterTap}'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart new file mode 100644 index 000000000000..167201906dd8 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cluster_manager_updates.dart @@ -0,0 +1,25 @@ +// 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 'types.dart'; + +/// [ClusterManager] update events to be applied to the [GoogleMap]. +/// +/// Used in [GoogleMapController] when the map is updated. +// (Do not re-export) +class ClusterManagerUpdates extends MapsObjectUpdates { + /// Computes [ClusterManagerUpdates] given previous and current [ClusterManager]s. + ClusterManagerUpdates.from(super.previous, super.current) + : super.from(objectName: 'clusterManager'); + + /// Set of Clusters to be added in this update. + Set get clusterManagersToAdd => objectsToAdd; + + /// Set of ClusterManagerIds to be removed in this update. + Set get clusterManagerIdsToRemove => + objectIdsToRemove.cast(); + + /// Set of Clusters to be changed in this update. + Set get clusterManagersToChange => objectsToChange; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart index 56f80e8312dd..009a6a078268 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart @@ -21,6 +21,7 @@ class MapObjects { this.polylines = const {}, this.circles = const {}, this.tileOverlays = const {}, + this.clusterManagers = const {}, }); final Set markers; @@ -28,4 +29,5 @@ class MapObjects { final Set polylines; final Set circles; final Set tileOverlays; + final Set clusterManagers; } 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 0b5b1a507d37..8c4d7b267979 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 @@ -151,6 +151,7 @@ class Marker implements MapsObject { this.rotation = 0.0, this.visible = true, this.zIndex = 0.0, + this.clusterManagerId, this.onTap, this.onDrag, this.onDragStart, @@ -163,6 +164,9 @@ class Marker implements MapsObject { @override MarkerId get mapsId => markerId; + /// Marker clustering is managed by [ClusterManager] with [clusterManagerId]. + final ClusterManagerId? clusterManagerId; + /// The opacity of the marker, between 0.0 and 1.0 inclusive. /// /// 0.0 means fully transparent, 1.0 means fully opaque. @@ -241,6 +245,7 @@ class Marker implements MapsObject { ValueChanged? onDragStartParam, ValueChanged? onDragParam, ValueChanged? onDragEndParam, + ClusterManagerId? clusterManagerIdParam, }) { return Marker( markerId: markerId, @@ -259,6 +264,7 @@ class Marker implements MapsObject { onDragStart: onDragStartParam ?? onDragStart, onDrag: onDragParam ?? onDrag, onDragEnd: onDragEndParam ?? onDragEnd, + clusterManagerId: clusterManagerIdParam ?? clusterManagerId, ); } @@ -289,6 +295,7 @@ class Marker implements MapsObject { addIfPresent('rotation', rotation); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); + addIfPresent('clusterManagerId', clusterManagerId?.value); return json; } @@ -312,7 +319,8 @@ class Marker implements MapsObject { position == other.position && rotation == other.rotation && visible == other.visible && - zIndex == other.zIndex; + zIndex == other.zIndex && + clusterManagerId == other.clusterManagerId; } @override @@ -324,6 +332,6 @@ class Marker implements MapsObject { '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}'; + 'onDrag: $onDrag, onDragEnd: $onDragEnd, clusterManagerId: $clusterManagerId}'; } } 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 1f1916b1c55e..3ef0e4ab18b5 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 @@ -9,6 +9,9 @@ export 'camera.dart'; export 'cap.dart'; export 'circle.dart'; export 'circle_updates.dart'; +export 'cluster.dart'; +export 'cluster_manager.dart'; +export 'cluster_manager_updates.dart'; export 'joint_type.dart'; export 'location.dart'; export 'map_configuration.dart'; @@ -30,6 +33,7 @@ export 'tile_provider.dart'; export 'ui.dart'; // Export the utils used by the Widget export 'utils/circle.dart'; +export 'utils/cluster_manager.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/utils/cluster_manager.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart new file mode 100644 index 000000000000..c44578c04d04 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/cluster_manager.dart @@ -0,0 +1,13 @@ +// 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 '../types.dart'; +import 'maps_object.dart'; + +/// Converts an [Iterable] of Cluster Managers in a Map of ClusterManagerId -> Cluster. +Map keyByClusterManagerId( + Iterable clusterManagers) { + return keyByMapsObjectId(clusterManagers) + .cast(); +} 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 8a712be78a77..ec9271aab715 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.5.0 +version: 2.6.0 environment: sdk: ^3.1.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 798559a68f0a..b0e48fd474bb 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 @@ -84,6 +84,35 @@ void main() { }, ); + test( + 'updateClusterManagers() throws UnimplementedError', + () { + expect( + () => BuildViewGoogleMapsFlutterPlatform().updateClusterManagers( + ClusterManagerUpdates.from( + { + const ClusterManager( + clusterManagerId: ClusterManagerId('123')) + }, + { + const ClusterManager( + clusterManagerId: ClusterManagerId('456')) + }, + ), + mapId: 0), + throwsUnimplementedError); + }, + ); + + test( + 'onClusterTap() throws UnimplementedError', + () { + expect( + () => BuildViewGoogleMapsFlutterPlatform().onClusterTap(mapId: 0), + throwsUnimplementedError); + }, + ); + test( 'default implementation of `getStyleError` returns null', () async { @@ -115,6 +144,7 @@ class BuildViewGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, + Set clusterManagers = const {}, Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart new file mode 100644 index 000000000000..073c6c3dbec9 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_manager_test.dart @@ -0,0 +1,57 @@ +// 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('$ClusterManager', () { + test('constructor defaults', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + + expect(manager.clusterManagerId, const ClusterManagerId('1234')); + }); + + test('toJson', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + + final Map json = manager.toJson() as Map; + + expect(json, { + 'clusterManagerId': '1234', + }); + }); + test('clone', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + final ClusterManager clone = manager.clone(); + + expect(identical(clone, manager), isFalse); + expect(clone, equals(manager)); + }); + test('copyWith', () { + const ClusterManager manager = + ClusterManager(clusterManagerId: ClusterManagerId('1234')); + final List log = []; + + final ClusterManager copy = manager.copyWith( + onClusterTapParam: (Cluster cluster) { + log.add('onTapParam'); + }, + ); + copy.onClusterTap!(Cluster( + manager.clusterManagerId, const [MarkerId('5678')], + position: const LatLng(11.0, 22.0), + bounds: LatLngBounds( + southwest: const LatLng(22.0, 33.0), + northeast: const LatLng(33.0, 88.0)))); + expect(log, contains('onTapParam')); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart new file mode 100644 index 000000000000..26a69ed0ee3b --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/cluster_test.dart @@ -0,0 +1,53 @@ +// 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('$Cluster', () { + test('constructor', () { + final Cluster cluster = Cluster( + const ClusterManagerId('3456787654'), + const [MarkerId('23456')], + position: const LatLng(55.0, 66.0), + bounds: LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + ), + ); + + expect(cluster.clusterManagerId.value, equals('3456787654')); + expect(cluster.markerIds[0].value, equals('23456')); + expect(cluster.position, equals(const LatLng(55.0, 66.0))); + expect( + cluster.bounds, + LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + )); + }); + + test('constructor markerIds length is > 0', () { + void initWithMarkerIds(List markerIds) { + Cluster( + const ClusterManagerId('3456787654'), + markerIds, + position: const LatLng(55.0, 66.0), + bounds: LatLngBounds( + northeast: const LatLng(88.0, 22.0), + southwest: const LatLng(11.0, 99.0), + ), + ); + } + + expect(() => initWithMarkerIds([const MarkerId('12342323')]), + isNot(throwsAssertionError)); + expect(() => initWithMarkerIds([]), throwsAssertionError); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart index db7afcbb0398..76e5de2b1866 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart @@ -96,7 +96,8 @@ void main() { expect(clone, equals(marker)); }); test('copyWith', () { - const Marker marker = Marker(markerId: MarkerId('ABC123')); + const MarkerId markerId = MarkerId('ABC123'); + const Marker marker = Marker(markerId: markerId); final BitmapDescriptor testDescriptor = BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); @@ -111,6 +112,8 @@ void main() { const double testRotationParam = 100; final bool testVisibleParam = !marker.visible; const double testZIndexParam = 100; + const ClusterManagerId testClusterManagerIdParam = + ClusterManagerId('DEF123'); final List log = []; final Marker copy = marker.copyWith( @@ -125,6 +128,7 @@ void main() { rotationParam: testRotationParam, visibleParam: testVisibleParam, zIndexParam: testZIndexParam, + clusterManagerIdParam: testClusterManagerIdParam, onTapParam: () { log.add('onTapParam'); }, @@ -139,6 +143,7 @@ void main() { }, ); + expect(copy.markerId, equals(markerId)); expect(copy.alpha, equals(testAlphaParam)); expect(copy.anchor, equals(testAnchorParam)); expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam)); @@ -150,6 +155,7 @@ void main() { expect(copy.rotation, equals(testRotationParam)); expect(copy.visible, equals(testVisibleParam)); expect(copy.zIndex, equals(testZIndexParam)); + expect(copy.clusterManagerId, equals(testClusterManagerIdParam)); copy.onTap!(); expect(log, contains('onTapParam')); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart new file mode 100644 index 000000000000..64eab27b2d4a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/cluster_manager_test.dart @@ -0,0 +1,30 @@ +// 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/src/types/types.dart'; + +void main() { + group('keyByClusterManagerId', () { + test('returns a Map keyed by clusterManagerId', () { + const ClusterManagerId id1 = ClusterManagerId('id1'); + const ClusterManagerId id2 = ClusterManagerId('id2'); + const ClusterManagerId id3 = ClusterManagerId('id3'); + + final List clusterManagers = [ + const ClusterManager(clusterManagerId: id1), + const ClusterManager(clusterManagerId: id2), + const ClusterManager(clusterManagerId: id3), + ]; + + final Map result = + keyByClusterManagerId(clusterManagers); + + expect(result, isA>()); + expect(result[id1], equals(clusterManagers[0])); + expect(result[id2], equals(clusterManagers[1])); + expect(result[id3], equals(clusterManagers[2])); + }); + }); +}