From 1215581ef0bc77552feb5b440b4efd70454d07d0 Mon Sep 17 00:00:00 2001 From: tenninebt Date: Mon, 8 Jun 2026 03:24:58 +0200 Subject: [PATCH 1/2] [google_maps_flutter] Add onPointOfInterestTap callback Expose a place-ID-only POI tap stream across the federated plugin stack (Android, iOS, web) so apps can react when users tap built-in map POIs. Fixes flutter/flutter#60695 --- .../google_maps_flutter/CHANGELOG.md | 4 + .../example/lib/map_click.dart | 17 ++ .../google_maps_flutter/example/pubspec.yaml | 7 + .../lib/google_maps_flutter.dart | 1 + .../lib/src/controller.dart | 5 + .../lib/src/google_map.dart | 13 ++ .../google_maps_flutter/pubspec.yaml | 17 +- .../fake_google_maps_flutter_platform.dart | 5 + .../test/google_maps_flutter_export_test.dart | 1 + .../test/point_of_interest_tap_test.dart | 51 ++++++ .../google_maps_flutter_android/CHANGELOG.md | 4 + .../googlemaps/GoogleMapController.java | 10 ++ .../plugins/googlemaps/GoogleMapListener.java | 1 + .../io/flutter/plugins/googlemaps/Messages.kt | 19 +++ .../googlemaps/GoogleMapControllerTest.java | 23 +++ .../example/pubspec.yaml | 4 + .../fake_google_maps_flutter_platform.dart | 5 + .../lib/src/google_maps_flutter_android.dart | 10 ++ .../lib/src/messages.g.dart | 28 ++++ .../pigeons/messages.dart | 3 + .../google_maps_flutter_android/pubspec.yaml | 8 +- .../google_maps_flutter_android_test.dart | 14 ++ .../google_maps_flutter_ios/CHANGELOG.md | 4 + .../ios/RunnerTests/GoogleMapsTests.swift | 132 ++++++++++++++- .../TestUtils/TestMapEventHandler.swift | 2 + .../example/pubspec.yaml | 4 + .../fake_google_maps_flutter_platform.dart | 5 + .../FGMGoogleMapController.m | 155 +++++++++++++++++- .../google_maps_flutter_pigeon_messages.g.m | 66 +++++--- .../FGMGoogleMapController_Test.h | 5 +- .../FGMMapEventDelegate.h | 3 + .../FGMMapsCallbackApiProtocol.h | 91 ++++++++++ .../google_maps_flutter_pigeon_messages.g.h | 3 + .../lib/src/google_maps_flutter_ios.dart | 10 ++ .../lib/src/messages.g.dart | 36 ++++ .../pigeons/messages.dart | 4 + .../google_maps_flutter_ios/pubspec.yaml | 8 +- .../test/google_maps_flutter_ios_test.dart | 14 ++ .../CHANGELOG.md | 4 + .../ios/RunnerTests/GoogleMapsTests.swift | 132 ++++++++++++++- .../TestUtils/TestMapEventHandler.swift | 2 + .../example/pubspec.yaml | 4 + .../fake_google_maps_flutter_platform.dart | 5 + .../FGMGoogleMapController.m | 155 +++++++++++++++++- .../google_maps_flutter_pigeon_messages.g.m | 66 +++++--- .../FGMGoogleMapController_Test.h | 5 +- .../FGMMapEventDelegate.h | 3 + .../FGMMapsCallbackApiProtocol.h | 91 ++++++++++ .../google_maps_flutter_pigeon_messages.g.h | 3 + .../lib/src/google_maps_flutter_ios.dart | 10 ++ .../lib/src/messages.g.dart | 36 ++++ .../pigeons/messages.dart | 4 + .../pubspec.yaml | 8 +- .../test/google_maps_flutter_ios_test.dart | 14 ++ .../google_maps_flutter_ios_sdk9/CHANGELOG.md | 4 + .../ios/RunnerTests/GoogleMapsTests.swift | 132 ++++++++++++++- .../TestUtils/TestMapEventHandler.swift | 2 + .../example/pubspec.yaml | 4 + .../fake_google_maps_flutter_platform.dart | 5 + .../FGMGoogleMapController.m | 155 +++++++++++++++++- .../google_maps_flutter_pigeon_messages.g.m | 66 +++++--- .../FGMGoogleMapController_Test.h | 5 +- .../FGMMapEventDelegate.h | 3 + .../FGMMapsCallbackApiProtocol.h | 91 ++++++++++ .../google_maps_flutter_pigeon_messages.g.h | 3 + .../lib/src/google_maps_flutter_ios.dart | 10 ++ .../lib/src/messages.g.dart | 36 ++++ .../pigeons/messages.dart | 4 + .../google_maps_flutter_ios_sdk9/pubspec.yaml | 8 +- .../test/google_maps_flutter_ios_test.dart | 14 ++ .../ios/RunnerTests/GoogleMapsTests.swift | 132 ++++++++++++++- .../TestUtils/TestMapEventHandler.swift | 2 + .../fake_google_maps_flutter_platform.dart | 5 + .../FGMGoogleMapController.m | 155 +++++++++++++++++- .../google_maps_flutter_pigeon_messages.g.m | 66 +++++--- .../FGMGoogleMapController_Test.h | 5 +- .../FGMMapEventDelegate.h | 3 + .../FGMMapsCallbackApiProtocol.h | 91 ++++++++++ .../google_maps_flutter_pigeon_messages.g.h | 3 + .../lib/src/google_maps_flutter_ios.dart | 10 ++ .../lib/src/messages.g.dart | 36 ++++ .../pigeons/messages.dart | 4 + .../test/google_maps_flutter_ios_test.dart | 14 ++ .../CHANGELOG.md | 4 + .../lib/src/events/map_event.dart | 9 + .../google_maps_flutter_platform.dart | 5 + .../lib/src/types/point_of_interest_id.dart | 16 ++ .../lib/src/types/types.dart | 1 + .../pubspec.yaml | 2 +- .../google_maps_flutter_platform_test.dart | 6 + .../test/types/point_of_interest_id_test.dart | 23 +++ .../google_maps_flutter_web/CHANGELOG.md | 4 + .../example/3-64/pubspec.yaml | 4 + .../google_maps_controller_test.dart | 30 ++++ .../google_maps_plugin_test.dart | 7 + .../example/latest/pubspec.yaml | 9 +- .../lib/google_maps_flutter_web.dart | 1 + .../lib/src/google_maps_controller.dart | 7 + .../lib/src/google_maps_flutter_web.dart | 5 + .../google_maps_flutter_web/pubspec.yaml | 8 +- 100 files changed, 2390 insertions(+), 153 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/test/point_of_interest_tap_test.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h create mode 100644 packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapsCallbackApiProtocol.h create mode 100644 packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapsCallbackApiProtocol.h create mode 100644 packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/point_of_interest_id.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/point_of_interest_id_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 056b3ffd66ba..3025a6663547 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.19.0 + +* Adds support for tapping points of interest on the map. + ## 2.18.0 * Adds support for mapTypeControlEnabled, fullscreenControlEnabled, and streetViewControlEnabled on web. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_click.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_click.dart index de3f13912cf7..446c34d3d823 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_click.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_click.dart @@ -35,6 +35,7 @@ class _MapClickBodyState extends State<_MapClickBody> { GoogleMapController? mapController; LatLng? _lastTap; LatLng? _lastLongPress; + String? _lastPointOfInterestPlaceId; @override Widget build(BuildContext context) { @@ -51,6 +52,11 @@ class _MapClickBodyState extends State<_MapClickBody> { _lastLongPress = pos; }); }, + onPointOfInterestTap: (PointOfInterestId pointOfInterestId) { + setState(() { + _lastPointOfInterestPlaceId = pointOfInterestId.value; + }); + }, ); final columnChildren = [ @@ -73,6 +79,17 @@ class _MapClickBodyState extends State<_MapClickBody> { child: Text(_lastLongPress != null ? 'Long pressed' : '', textAlign: TextAlign.center), ), ); + final lastPointOfInterestTap = + 'Point of interest place ID:\n${_lastPointOfInterestPlaceId ?? ""}'; + columnChildren.add(Center(child: Text(lastPointOfInterestTap, textAlign: TextAlign.center))); + columnChildren.add( + Center( + child: Text( + _lastPointOfInterestPlaceId != null ? 'Point of interest tapped' : '', + textAlign: TextAlign.center, + ), + ), + ); } return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren); } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index 9481704ea85f..82e2db388923 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -32,3 +32,10 @@ flutter: uses-material-design: true assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_android: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_android} + google_maps_flutter_ios: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_ios} + google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} + google_maps_flutter_web: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_web} diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart index 931dd16a7211..37afda8be256 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart @@ -54,6 +54,7 @@ export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf MinMaxZoomPreference, PatternItem, PinConfig, + PointOfInterestId, Polygon, PolygonId, Polyline, diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 32ed8a26671f..d5b6840d6db9 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -59,6 +59,11 @@ class GoogleMapController { .listen((_) => _googleMapState.widget.onCameraIdle!()), ); } + _streamSubscriptions.add( + GoogleMapsFlutterPlatform.instance + .onPointOfInterestTap(mapId: mapId) + .listen((PointOfInterestTapEvent e) => _googleMapState.onPointOfInterestTap(e.value)), + ); _streamSubscriptions.add( GoogleMapsFlutterPlatform.instance .onMarkerTap(mapId: mapId) diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index d90b7aa175b4..0994466559b0 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -136,6 +136,7 @@ class GoogleMap extends StatefulWidget { this.onCameraIdle, this.onTap, this.onLongPress, + this.onPointOfInterestTap, this.markerType = GoogleMapMarkerType.marker, this.colorScheme, String? mapId, @@ -300,6 +301,11 @@ class GoogleMap extends StatefulWidget { /// Called every time a [GoogleMap] is long pressed. final ArgumentCallback? onLongPress; + /// Called when a point of interest on the map is tapped. + /// + /// May not be supported on all platforms. + final ArgumentCallback? onPointOfInterestTap; + /// True if a "My Location" layer should be shown on the map. /// /// This layer includes a location indicator at the current device location, @@ -703,6 +709,13 @@ class _GoogleMapState extends State { } } + void onPointOfInterestTap(PointOfInterestId pointOfInterestId) { + final ArgumentCallback? onPointOfInterestTap = widget.onPointOfInterestTap; + if (onPointOfInterestTap != null) { + onPointOfInterestTap(pointOfInterestId); + } + } + void onTap(LatLng position) { final ArgumentCallback? onTap = widget.onTap; if (onTap != null) { diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index d7737ac9e945..c68d4d7d8447 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.18.0 +version: 2.19.0 environment: sdk: ^3.10.0 @@ -21,10 +21,10 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_android: ^2.19.1 - google_maps_flutter_ios: ^2.18.0 - google_maps_flutter_platform_interface: ^2.16.0 - google_maps_flutter_web: ^0.6.3 + google_maps_flutter_android: ^2.20.0 + google_maps_flutter_ios: ^2.19.0 + google_maps_flutter_platform_interface: ^2.17.0 + google_maps_flutter_web: ^0.6.4 dev_dependencies: flutter_test: @@ -41,3 +41,10 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /example/web/index.html +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_android: {path: ../../../packages/google_maps_flutter/google_maps_flutter_android} + google_maps_flutter_ios: {path: ../../../packages/google_maps_flutter/google_maps_flutter_ios} + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} + google_maps_flutter_web: {path: ../../../packages/google_maps_flutter/google_maps_flutter_web} diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart index fae706bdb4ed..ffb6303ee4d3 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart @@ -227,6 +227,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_maps_flutter_export_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_maps_flutter_export_test.dart index decbeee5292c..c148561cd6ce 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_maps_flutter_export_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_maps_flutter_export_test.dart @@ -50,6 +50,7 @@ void main() { main_file.MinMaxZoomPreference; main_file.PatternItem; main_file.PinConfig; + main_file.PointOfInterestId; main_file.Polygon; main_file.PolygonId; main_file.Polyline; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/point_of_interest_tap_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/point_of_interest_tap_test.dart new file mode 100644 index 000000000000..9451be1825db --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/test/point_of_interest_tap_test.dart @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +import 'fake_google_maps_flutter_platform.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late FakeGoogleMapsFlutterPlatform platform; + + setUp(() { + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; + }); + + testWidgets('onPointOfInterestTap callback receives place ID from platform event', ( + WidgetTester tester, + ) async { + PointOfInterestId? tappedPointOfInterestId; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), + onPointOfInterestTap: (PointOfInterestId pointOfInterestId) { + tappedPointOfInterestId = pointOfInterestId; + }, + ), + ), + ); + + await tester.pump(); + + expect(platform.createdIds, isNotEmpty); + final int mapId = platform.createdIds.first; + + platform.mapEventStreamController.add( + PointOfInterestTapEvent(mapId, const PointOfInterestId('place-123')), + ); + + await tester.pump(); + + expect(tappedPointOfInterestId, const PointOfInterestId('place-123')); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index c62c53095ef3..5da702010381 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.20.0 + +* Adds support for tapping points of interest on the map. + ## 2.19.12 * Bumps the androidx group across 10 directories with 1 update. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 273e0989cbfe..56a4716b88fd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -38,6 +38,7 @@ import com.google.android.gms.maps.model.MapCapabilities; import com.google.android.gms.maps.model.MapStyleOptions; import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.PointOfInterest; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.TileOverlay; @@ -385,6 +386,14 @@ public void onCircleClick(Circle circle) { circlesController.onCircleTap(circle.getId()); } + @Override + public void onPoiClick(PointOfInterest pointOfInterest) { + if (pointOfInterest.placeId != null) { + flutterApi.onPointOfInterestTap( + pointOfInterest.placeId, (Result result) -> Unit.INSTANCE); + } + } + @Override public void onGroundOverlayClick(@NonNull GroundOverlay groundOverlay) { groundOverlaysController.onGroundOverlayTap(groundOverlay.getId()); @@ -421,6 +430,7 @@ private void setGoogleMapListener(@Nullable GoogleMapListener listener) { googleMap.setOnPolygonClickListener(listener); googleMap.setOnPolylineClickListener(listener); googleMap.setOnCircleClickListener(listener); + googleMap.setOnPoiClickListener(listener); googleMap.setOnMapClickListener(listener); googleMap.setOnMapLongClickListener(listener); googleMap.setOnGroundOverlayClickListener(listener); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java index a9a5ae7f86ba..2816c5ebe124 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java @@ -15,6 +15,7 @@ interface GoogleMapListener GoogleMap.OnPolygonClickListener, GoogleMap.OnPolylineClickListener, GoogleMap.OnCircleClickListener, + GoogleMap.OnPoiClickListener, GoogleMap.OnMapClickListener, GoogleMap.OnMapLongClickListener, GoogleMap.OnMarkerDragListener, diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/kotlin/io/flutter/plugins/googlemaps/Messages.kt b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/kotlin/io/flutter/plugins/googlemaps/Messages.kt index 3d5d0190d6a1..e6d78829c242 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/kotlin/io/flutter/plugins/googlemaps/Messages.kt +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/kotlin/io/flutter/plugins/googlemaps/Messages.kt @@ -4071,6 +4071,25 @@ class MapsCallbackApi( } } } + /** Called when a point of interest is tapped. */ + fun onPointOfInterestTap(placeIdArg: String, callback: (Result) -> Unit) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = + "dev.flutter.pigeon.google_maps_flutter_android.MapsCallbackApi.onPointOfInterestTap$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(placeIdArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(MessagesPigeonUtils.createConnectionError(channelName))) + } + } + } /** Called when a marker cluster is tapped. */ fun onClusterTap(clusterArg: PlatformCluster, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 38c95e18f3f3..c3fabeb7476e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -27,6 +27,7 @@ import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MapCapabilities; import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.PointOfInterest; import com.google.maps.android.clustering.ClusterManager; import io.flutter.plugin.common.BinaryMessenger; import java.util.ArrayList; @@ -258,6 +259,28 @@ public void OnClusterItemInfoWindowClickCallsMarkersController() { verify(mockMarkersController, times(1)).onClusterItemInfoWindowTap(markerBuilder.markerId()); } + @Test + public void OnPoiClickCallsFlutterApi() { + GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); + googleMapController.onMapReady(mockGoogleMap); + + PointOfInterest pointOfInterest = + new PointOfInterest(new LatLng(0, 0), "place-123", "Test Place"); + googleMapController.onPoiClick(pointOfInterest); + + verify(flutterApi, times(1)).onPointOfInterestTap(eq("place-123"), any()); + } + + @Test + public void OnPoiClickNullPlaceIdDoesNotCallFlutterApi() { + GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); + googleMapController.onMapReady(mockGoogleMap); + + googleMapController.onPoiClick(new PointOfInterest(new LatLng(0, 0), null, "Test Place")); + + verify(flutterApi, times(0)).onPointOfInterestTap(any(), any()); + } + @Test public void SetInitialHeatmaps() { GoogleMapController googleMapController = getGoogleMapControllerWithMockedDependencies(); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml index 9a3889d52d48..5acc60e455a8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml @@ -33,3 +33,7 @@ flutter: uses-material-design: true assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart index 654c0b80a4e4..29b422cc29ec 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/test/fake_google_maps_flutter_platform.dart @@ -206,6 +206,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 9403484d2289..c5a87ef525fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -205,6 +205,11 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return _events(mapId).whereType(); @@ -1079,6 +1084,11 @@ class HostMapMessageHandler implements MapsCallbackApi { streamController.add(CircleTapEvent(mapId, CircleId(circleId))); } + @override + void onPointOfInterestTap(String placeId) { + streamController.add(PointOfInterestTapEvent(mapId, PointOfInterestId(placeId))); + } + @override void onClusterTap(PlatformCluster cluster) { streamController.add( diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index 857e1aeb7337..f4b80671afc4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -3436,6 +3436,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); @@ -3731,6 +3734,31 @@ abstract class MapsCallbackApi { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_maps_flutter_android.MapsCallbackApi.onPointOfInterestTap$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_placeId = args[0]! as String; + try { + api.onPointOfInterestTap(arg_placeId); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.google_maps_flutter_android.MapsCallbackApi.onClusterTap$messageChannelSuffix', diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 2b14821fb396..24bbebe55438 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -831,6 +831,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index 8d34180d23a9..1f5c1513caed 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.19.12 +version: 2.20.0 environment: sdk: ^3.12.0 @@ -21,7 +21,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 - google_maps_flutter_platform_interface: ^2.13.0 + google_maps_flutter_platform_interface: ^2.17.0 meta: ^1.10.0 stream_transform: ^2.0.0 @@ -38,3 +38,7 @@ topics: - google-maps - google-maps-flutter - map +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 968558a9d0c0..63395b99517b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -978,6 +978,20 @@ void main() { expect((await stream.next).value.value, equals(objectId)); }); + test('points of interest send tap events to correct stream', () async { + const mapId = 1; + const placeId = 'place-123'; + + final maps = GoogleMapsFlutterAndroid(); + final HostMapMessageHandler callbackHandler = maps.ensureHandlerInitialized(mapId); + + final stream = StreamQueue(maps.onPointOfInterestTap(mapId: mapId)); + + callbackHandler.onPointOfInterestTap(placeId); + + expect((await stream.next).value.value, equals(placeId)); + }); + test('clusters send tap events to correct stream', () async { const mapId = 1; const managerId = 'manager-id'; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index b4326e884c99..ed975cff0ea2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.19.0 + +* Adds support for tapping points of interest on the map. + ## 2.18.4 * Fixes a potential compilation issue in tile downscaling. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.swift b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.swift index bcf512afe221..a95e7a2830a5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.swift @@ -38,6 +38,98 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Fake implementation of FGMMapsCallbackApiProtocol that records the calls it receives. +class MockMapsCallbackApi: NSObject, FGMMapsCallbackApiProtocol { + var lastTappedPointOfInterestPlaceIdentifier: String? + + func didTapPointOfInterest( + withPlaceIdentifier placeIdentifier: String, + completion: @escaping (FlutterError?) -> Void + ) { + lastTappedPointOfInterestPlaceIdentifier = placeIdentifier + completion(nil) + } + + func didStartCameraMove(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didMoveCamera( + to cameraPosition: FGMPlatformCameraPosition, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didIdleCamera(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didTap( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didLongPress( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didStartDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didDragMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didEndDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapInfoWindowOfMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapCircle( + withIdentifier circleId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTap( + _ cluster: FGMPlatformCluster, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolygon( + withIdentifier polygonId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolyline( + withIdentifier polylineId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapGroundOverlay( + withIdentifier groundOverlayId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func tile( + withOverlayIdentifier tileOverlayId: String, + location: FGMPlatformPoint, + zoom: Int, + completion: @escaping (FGMPlatformTile?, FlutterError?) -> Void + ) { completion(nil, nil) } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -74,7 +166,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) for _ in 0..<10 { @@ -133,7 +226,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -165,7 +259,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -188,6 +283,34 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == durationMilliseconds.doubleValue / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() { + let frame = CGRect(x: 0, y: 0, width: 100, height: 100) + let mapViewOptions = GMSMapViewOptions() + mapViewOptions.frame = frame + mapViewOptions.camera = GMSCameraPosition(latitude: 0, longitude: 0, zoom: 0) + + let mapView = PartiallyMockedMapView(options: mapViewOptions) + + let callbackApi = MockMapsCallbackApi() + let controller = FGMGoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: callbackApi + ) + + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + + #expect(callbackApi.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() @@ -205,7 +328,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: binaryMessenger + binaryMessenger: binaryMessenger, + callbackHandler: MockMapsCallbackApi() ) let inspector = FGMMapInspector( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift index 121738f6c540..4fe0daa2f3a1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift @@ -32,6 +32,8 @@ class TestMapEventHandler: NSObject, FGMMapEventDelegate { func didTapCircle(withIdentifier circleId: String) {} + func didTapPointOfInterest(withPlaceIdentifier placeIdentifier: String) {} + func didTap(_ cluster: FGMPlatformCluster) {} func didTapPolygon(withIdentifier polygonId: String) {} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml index 706a48387c0e..71b0ee5281d4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml @@ -31,3 +31,7 @@ flutter: uses-material-design: true assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/test/fake_google_maps_flutter_platform.dart index 3e8beefc78c4..f4bc1fd82c8f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/test/fake_google_maps_flutter_platform.dart @@ -206,6 +206,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m index 5f609c9b00de..97aa8651f75a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m @@ -11,6 +11,7 @@ #import "FGMConversionUtils.h" #import "FGMGroundOverlayController.h" #import "FGMHeatmapController.h" +#import "FGMMapsCallbackApiProtocol.h" #import "FGMMarkerUserData.h" #import "FGMTileOverlayController.h" #import "google_maps_flutter_pigeon_messages.g.h" @@ -99,17 +100,138 @@ - (UIImage *)imageNamed:(NSString *)name { #pragma mark - +/// Non-test implementation of FGMMapsCallbackApiProtocol, passing calls through to a +/// FGMMapsCallbackApi instance. +@interface FGMDefaultMapsCallbackApi : NSObject +@property(strong, nonatomic) FGMMapsCallbackApi *callbackApi; + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix; +@end + +@implementation FGMDefaultMapsCallbackApi + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix { + self = [super init]; + if (self) { + _callbackApi = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger + messageChannelSuffix:messageChannelSuffix]; + } + return self; +} + +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartCameraMoveWithCompletion:completion]; +} + +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didMoveCameraToPosition:cameraPosition completion:completion]; +} + +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didIdleCameraWithCompletion:completion]; +} + +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapAtPosition:position completion:completion]; +} + +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didLongPressAtPosition:position completion:completion]; +} + +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didDragMarkerWithIdentifier:markerId atPosition:position completion:completion]; +} + +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didEndDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapInfoWindowOfMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCircleWithIdentifier:circleId completion:completion]; +} + +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPointOfInterestWithPlaceIdentifier:placeIdentifier completion:completion]; +} + +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCluster:cluster completion:completion]; +} + +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolygonWithIdentifier:polygonId completion:completion]; +} + +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolylineWithIdentifier:polylineId completion:completion]; +} + +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapGroundOverlayWithIdentifier:groundOverlayId completion:completion]; +} + +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion { + [self.callbackApi tileWithOverlayIdentifier:tileOverlayId + location:location + zoom:zoom + completion:completion]; +} + +@end + +#pragma mark - + /// Non-test implementation of FGMAssetProvider, wrapping a FGMMapsCallbackApi /// instance. @interface FGMDefaultMapEventHandler : NSObject -@property(strong, nonatomic) FGMMapsCallbackApi *callbackHandler; +@property(strong, nonatomic) id callbackHandler; -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler; +- (instancetype)initWithCallbackHandler:(id)callbackHandler; @end @implementation FGMDefaultMapEventHandler -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler { +- (instancetype)initWithCallbackHandler:(id)callbackHandler { self = [super init]; if (self) { _callbackHandler = callbackHandler; @@ -186,6 +308,12 @@ - (void)didTapCircleWithIdentifier:(NSString *)circleId { }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier { + [self.callbackHandler didTapPointOfInterestWithPlaceIdentifier:placeIdentifier + completion:^(FlutterError *_){ + }]; +} + - (void)didTapCluster:(FGMPlatformCluster *)cluster { [self.callbackHandler didTapCluster:cluster completion:^(FlutterError *_){ @@ -246,7 +374,7 @@ @interface FGMMapInspector () @interface FGMGoogleMapController () @property(nonatomic, strong) GMSMapView *mapView; -@property(nonatomic, strong) FGMMapsCallbackApi *dartCallbackHandler; +@property(nonatomic, strong) id dartCallbackHandler; @property(nonatomic, strong) FGMDefaultMapEventHandler *mapEventHandler; @property(nonatomic, assign) BOOL trackCameraPosition; @property(nonatomic, strong) FGMClusterManagersController *clusterManagersController; @@ -290,18 +418,23 @@ - (instancetype)initWithFrame:(CGRect)frame GMSMapView *mapView = [[GMSMapView alloc] initWithOptions:options]; + NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; return [self initWithMapView:mapView viewIdentifier:viewId creationParameters:creationParameters assetProvider:[[FGMDefaultAssetProvider alloc] initWithRegistrar:registrar] - binaryMessenger:registrar.messenger]; + binaryMessenger:registrar.messenger + callbackHandler:[[FGMDefaultMapsCallbackApi alloc] + initWithBinaryMessenger:registrar.messenger + messageChannelSuffix:pigeonSuffix]]; } - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger { + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler { if (self = [super init]) { _mapView = mapView; @@ -310,8 +443,7 @@ - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView // https://github.com/flutter/flutter/issues/104121 [self interpretMapConfiguration:creationParameters.mapConfiguration]; NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; - _dartCallbackHandler = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger - messageChannelSuffix:pigeonSuffix]; + _dartCallbackHandler = callbackHandler; _mapEventHandler = [[FGMDefaultMapEventHandler alloc] initWithCallbackHandler:_dartCallbackHandler]; FGMPlatformMarkerType markerType = creationParameters.mapConfiguration.markerType; @@ -558,6 +690,13 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { } } +- (void)mapView:(GMSMapView *)mapView + didTapPOIWithPlaceID:(NSString *)placeID + name:(NSString *)name + location:(CLLocationCoordinate2D)location { + [self.mapEventHandler didTapPointOfInterestWithPlaceIdentifier:placeID]; +} + - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.mapEventHandler didTapAtPosition:FGMGetPigeonLatLngForCoordinate(coordinate)]; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m index 3302fd9e13b8..1ebe8dd250be 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m @@ -2336,11 +2336,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updatePolylinesByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updatePolylinesByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updatePolylinesByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updatePolylinesByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2367,11 +2367,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateTileOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateTileOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2398,11 +2398,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateGroundOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateGroundOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2631,11 +2631,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier: - error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_markerId = GetNullableObjectAtIndex(args, 0); @@ -3068,6 +3068,32 @@ - (void)didTapCircleWithIdentifier:(NSString *)arg_circleId } }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)arg_placeId + completion:(void (^)(FlutterError *_Nullable))completion { + NSString *channelName = [NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap", + _messageChannelSuffix]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; + [channel sendMessage:@[ arg_placeId ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion(createConnectionError(channelName)); + } + }]; +} - (void)didTapCluster:(FGMPlatformCluster *)arg_cluster completion:(void (^)(FlutterError *_Nullable))completion { NSString *channelName = [NSString diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h index 5e1fa9371b2c..67a27b1433fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h @@ -8,6 +8,7 @@ #import "FGMAssetProvider.h" #import "FGMCATransactionWrapper.h" #import "FGMGoogleMapController.h" +#import "FGMMapsCallbackApiProtocol.h" NS_ASSUME_NONNULL_BEGIN @@ -48,11 +49,13 @@ NS_ASSUME_NONNULL_BEGIN /// @param creationParameters Parameters for initialising the map view. /// @param assetProvider The asset provider to use for looking up assets. /// @param binaryMessenger The binary messenger to use for sending messages to Dart. +/// @param callbackHandler The callback API to use for sending events to Dart. - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger; + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler; // The main Pigeon API implementation. @property(nonatomic, strong, readonly) FGMMapCallHandler *callHandler; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h index f93f1adef252..ee6f9ee5db38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h @@ -50,6 +50,9 @@ NS_ASSUME_NONNULL_BEGIN /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier; + /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h new file mode 100644 index 000000000000..4841dd6e7d9a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import Foundation; + +#import "google_maps_flutter_pigeon_messages.g.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Protocol for FGMMapsCallbackApi to allow mocking in tests. +/// +/// This is a one-to-one abstraction of the Pigeon-generated API, so that unit tests can inject a +/// fake in place of the real implementation rather than asserting on Pigeon channel internals. +@protocol FGMMapsCallbackApiProtocol + +/// Called when the map camera starts moving. +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera moves. +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera stops moving. +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is tapped. +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is long pressed. +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker is tapped. +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag starts. +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag updates. +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag ends. +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker's info window is tapped. +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a circle is tapped. +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker cluster is tapped. +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polygon is tapped. +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polyline is tapped. +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a ground overlay is tapped. +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called to get data for a map tile. +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h index 053f0f577650..1db7c8122b19 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h @@ -910,6 +910,9 @@ extern void SetUpFGMMapsApiWithSuffix(id binaryMessenger /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId completion:(void (^)(FlutterError *_Nullable))completion; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeId + completion:(void (^)(FlutterError *_Nullable))completion; /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index c0c8db027c8d..157bce4cbaf6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -182,6 +182,11 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return _events(mapId).whereType(); @@ -989,6 +994,11 @@ class HostMapMessageHandler implements MapsCallbackApi { streamController.add(CircleTapEvent(mapId, CircleId(circleId))); } + @override + void onPointOfInterestTap(String placeId) { + streamController.add(PointOfInterestTapEvent(mapId, PointOfInterestId(placeId))); + } + @override void onClusterTap(PlatformCluster cluster) { streamController.add( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/messages.g.dart index be134f058b12..db2d58fe4335 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/messages.g.dart @@ -3355,6 +3355,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); @@ -3734,6 +3737,39 @@ abstract class MapsCallbackApi { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null.', + ); + final List args = (message as List?)!; + final String? arg_placeId = (args[0] as String?); + assert( + arg_placeId != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null, expected non-null String.', + ); + try { + api.onPointOfInterestTap(arg_placeId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onClusterTap$messageChannelSuffix', diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_ios/pigeons/messages.dart index a04e857d05fc..017595dfa6dd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pigeons/messages.dart @@ -832,6 +832,10 @@ abstract class MapsCallbackApi { @ObjCSelector('didTapCircleWithIdentifier:') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @ObjCSelector('didTapPointOfInterestWithPlaceIdentifier:') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @ObjCSelector('didTapCluster:') void onClusterTap(PlatformCluster cluster); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index c5fe13b0ac51..dc0b51babed9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios description: iOS implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.18.4 +version: 2.19.0 environment: sdk: ^3.10.0 @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.14.2 + google_maps_flutter_platform_interface: ^2.17.0 stream_transform: ^2.0.0 dev_dependencies: @@ -37,3 +37,7 @@ topics: - google-maps - google-maps-flutter - map +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart index da996d1b8e6e..410ddd412e85 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart @@ -905,6 +905,20 @@ void main() { expect((await stream.next).value.value, equals(objectId)); }); + test('points of interest send tap events to correct stream', () async { + const mapId = 1; + const placeId = 'place-123'; + + final maps = GoogleMapsFlutterIOS(); + final HostMapMessageHandler callbackHandler = maps.ensureHandlerInitialized(mapId); + + final stream = StreamQueue(maps.onPointOfInterestTap(mapId: mapId)); + + callbackHandler.onPointOfInterestTap(placeId); + + expect((await stream.next).value.value, equals(placeId)); + }); + test('clusters send tap events to correct stream', () async { const mapId = 1; const managerId = 'manager-id'; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/CHANGELOG.md index 59856f19dbec..731ca2d7cee8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.19.0 + +* Adds support for tapping points of interest on the map. + ## 2.18.5 * Fixes a potential compilation issue in tile downscaling. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/GoogleMapsTests.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/GoogleMapsTests.swift index ea77ef73992b..b3ea2eda56a9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/GoogleMapsTests.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/GoogleMapsTests.swift @@ -38,6 +38,98 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Fake implementation of FGMMapsCallbackApiProtocol that records the calls it receives. +class MockMapsCallbackApi: NSObject, FGMMapsCallbackApiProtocol { + var lastTappedPointOfInterestPlaceIdentifier: String? + + func didTapPointOfInterest( + withPlaceIdentifier placeIdentifier: String, + completion: @escaping (FlutterError?) -> Void + ) { + lastTappedPointOfInterestPlaceIdentifier = placeIdentifier + completion(nil) + } + + func didStartCameraMove(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didMoveCamera( + to cameraPosition: FGMPlatformCameraPosition, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didIdleCamera(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didTap( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didLongPress( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didStartDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didDragMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didEndDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapInfoWindowOfMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapCircle( + withIdentifier circleId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTap( + _ cluster: FGMPlatformCluster, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolygon( + withIdentifier polygonId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolyline( + withIdentifier polylineId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapGroundOverlay( + withIdentifier groundOverlayId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func tile( + withOverlayIdentifier tileOverlayId: String, + location: FGMPlatformPoint, + zoom: Int, + completion: @escaping (FGMPlatformTile?, FlutterError?) -> Void + ) { completion(nil, nil) } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -74,7 +166,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) for _ in 0..<10 { @@ -133,7 +226,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -165,7 +259,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -188,6 +283,34 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == durationMilliseconds.doubleValue / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() { + let frame = CGRect(x: 0, y: 0, width: 100, height: 100) + let mapViewOptions = GMSMapViewOptions() + mapViewOptions.frame = frame + mapViewOptions.camera = GMSCameraPosition(latitude: 0, longitude: 0, zoom: 0) + + let mapView = PartiallyMockedMapView(options: mapViewOptions) + + let callbackApi = MockMapsCallbackApi() + let controller = FGMGoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: callbackApi + ) + + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + + #expect(callbackApi.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() @@ -205,7 +328,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: binaryMessenger + binaryMessenger: binaryMessenger, + callbackHandler: MockMapsCallbackApi() ) let inspector = FGMMapInspector( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift index 86b567e6adcc..5ef1d1ad905c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift @@ -32,6 +32,8 @@ class TestMapEventHandler: NSObject, FGMMapEventDelegate { func didTapCircle(withIdentifier circleId: String) {} + func didTapPointOfInterest(withPlaceIdentifier placeIdentifier: String) {} + func didTap(_ cluster: FGMPlatformCluster) {} func didTapPolygon(withIdentifier polygonId: String) {} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/pubspec.yaml index 7d916be0452d..c778bab150e2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/pubspec.yaml @@ -31,3 +31,7 @@ flutter: uses-material-design: true assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/test/fake_google_maps_flutter_platform.dart index 3e8beefc78c4..f4bc1fd82c8f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/example/test/fake_google_maps_flutter_platform.dart @@ -206,6 +206,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/FGMGoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/FGMGoogleMapController.m index 5f609c9b00de..97aa8651f75a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/FGMGoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/FGMGoogleMapController.m @@ -11,6 +11,7 @@ #import "FGMConversionUtils.h" #import "FGMGroundOverlayController.h" #import "FGMHeatmapController.h" +#import "FGMMapsCallbackApiProtocol.h" #import "FGMMarkerUserData.h" #import "FGMTileOverlayController.h" #import "google_maps_flutter_pigeon_messages.g.h" @@ -99,17 +100,138 @@ - (UIImage *)imageNamed:(NSString *)name { #pragma mark - +/// Non-test implementation of FGMMapsCallbackApiProtocol, passing calls through to a +/// FGMMapsCallbackApi instance. +@interface FGMDefaultMapsCallbackApi : NSObject +@property(strong, nonatomic) FGMMapsCallbackApi *callbackApi; + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix; +@end + +@implementation FGMDefaultMapsCallbackApi + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix { + self = [super init]; + if (self) { + _callbackApi = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger + messageChannelSuffix:messageChannelSuffix]; + } + return self; +} + +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartCameraMoveWithCompletion:completion]; +} + +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didMoveCameraToPosition:cameraPosition completion:completion]; +} + +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didIdleCameraWithCompletion:completion]; +} + +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapAtPosition:position completion:completion]; +} + +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didLongPressAtPosition:position completion:completion]; +} + +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didDragMarkerWithIdentifier:markerId atPosition:position completion:completion]; +} + +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didEndDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapInfoWindowOfMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCircleWithIdentifier:circleId completion:completion]; +} + +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPointOfInterestWithPlaceIdentifier:placeIdentifier completion:completion]; +} + +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCluster:cluster completion:completion]; +} + +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolygonWithIdentifier:polygonId completion:completion]; +} + +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolylineWithIdentifier:polylineId completion:completion]; +} + +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapGroundOverlayWithIdentifier:groundOverlayId completion:completion]; +} + +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion { + [self.callbackApi tileWithOverlayIdentifier:tileOverlayId + location:location + zoom:zoom + completion:completion]; +} + +@end + +#pragma mark - + /// Non-test implementation of FGMAssetProvider, wrapping a FGMMapsCallbackApi /// instance. @interface FGMDefaultMapEventHandler : NSObject -@property(strong, nonatomic) FGMMapsCallbackApi *callbackHandler; +@property(strong, nonatomic) id callbackHandler; -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler; +- (instancetype)initWithCallbackHandler:(id)callbackHandler; @end @implementation FGMDefaultMapEventHandler -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler { +- (instancetype)initWithCallbackHandler:(id)callbackHandler { self = [super init]; if (self) { _callbackHandler = callbackHandler; @@ -186,6 +308,12 @@ - (void)didTapCircleWithIdentifier:(NSString *)circleId { }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier { + [self.callbackHandler didTapPointOfInterestWithPlaceIdentifier:placeIdentifier + completion:^(FlutterError *_){ + }]; +} + - (void)didTapCluster:(FGMPlatformCluster *)cluster { [self.callbackHandler didTapCluster:cluster completion:^(FlutterError *_){ @@ -246,7 +374,7 @@ @interface FGMMapInspector () @interface FGMGoogleMapController () @property(nonatomic, strong) GMSMapView *mapView; -@property(nonatomic, strong) FGMMapsCallbackApi *dartCallbackHandler; +@property(nonatomic, strong) id dartCallbackHandler; @property(nonatomic, strong) FGMDefaultMapEventHandler *mapEventHandler; @property(nonatomic, assign) BOOL trackCameraPosition; @property(nonatomic, strong) FGMClusterManagersController *clusterManagersController; @@ -290,18 +418,23 @@ - (instancetype)initWithFrame:(CGRect)frame GMSMapView *mapView = [[GMSMapView alloc] initWithOptions:options]; + NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; return [self initWithMapView:mapView viewIdentifier:viewId creationParameters:creationParameters assetProvider:[[FGMDefaultAssetProvider alloc] initWithRegistrar:registrar] - binaryMessenger:registrar.messenger]; + binaryMessenger:registrar.messenger + callbackHandler:[[FGMDefaultMapsCallbackApi alloc] + initWithBinaryMessenger:registrar.messenger + messageChannelSuffix:pigeonSuffix]]; } - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger { + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler { if (self = [super init]) { _mapView = mapView; @@ -310,8 +443,7 @@ - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView // https://github.com/flutter/flutter/issues/104121 [self interpretMapConfiguration:creationParameters.mapConfiguration]; NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; - _dartCallbackHandler = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger - messageChannelSuffix:pigeonSuffix]; + _dartCallbackHandler = callbackHandler; _mapEventHandler = [[FGMDefaultMapEventHandler alloc] initWithCallbackHandler:_dartCallbackHandler]; FGMPlatformMarkerType markerType = creationParameters.mapConfiguration.markerType; @@ -558,6 +690,13 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { } } +- (void)mapView:(GMSMapView *)mapView + didTapPOIWithPlaceID:(NSString *)placeID + name:(NSString *)name + location:(CLLocationCoordinate2D)location { + [self.mapEventHandler didTapPointOfInterestWithPlaceIdentifier:placeID]; +} + - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.mapEventHandler didTapAtPosition:FGMGetPigeonLatLngForCoordinate(coordinate)]; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.m b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.m index 3302fd9e13b8..1ebe8dd250be 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.m @@ -2336,11 +2336,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updatePolylinesByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updatePolylinesByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updatePolylinesByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updatePolylinesByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2367,11 +2367,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateTileOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateTileOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2398,11 +2398,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateGroundOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateGroundOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2631,11 +2631,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier: - error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_markerId = GetNullableObjectAtIndex(args, 0); @@ -3068,6 +3068,32 @@ - (void)didTapCircleWithIdentifier:(NSString *)arg_circleId } }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)arg_placeId + completion:(void (^)(FlutterError *_Nullable))completion { + NSString *channelName = [NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap", + _messageChannelSuffix]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; + [channel sendMessage:@[ arg_placeId ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion(createConnectionError(channelName)); + } + }]; +} - (void)didTapCluster:(FGMPlatformCluster *)arg_cluster completion:(void (^)(FlutterError *_Nullable))completion { NSString *channelName = [NSString diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMGoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMGoogleMapController_Test.h index 5e1fa9371b2c..67a27b1433fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMGoogleMapController_Test.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMGoogleMapController_Test.h @@ -8,6 +8,7 @@ #import "FGMAssetProvider.h" #import "FGMCATransactionWrapper.h" #import "FGMGoogleMapController.h" +#import "FGMMapsCallbackApiProtocol.h" NS_ASSUME_NONNULL_BEGIN @@ -48,11 +49,13 @@ NS_ASSUME_NONNULL_BEGIN /// @param creationParameters Parameters for initialising the map view. /// @param assetProvider The asset provider to use for looking up assets. /// @param binaryMessenger The binary messenger to use for sending messages to Dart. +/// @param callbackHandler The callback API to use for sending events to Dart. - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger; + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler; // The main Pigeon API implementation. @property(nonatomic, strong, readonly) FGMMapCallHandler *callHandler; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapEventDelegate.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapEventDelegate.h index f93f1adef252..ee6f9ee5db38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapEventDelegate.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapEventDelegate.h @@ -50,6 +50,9 @@ NS_ASSUME_NONNULL_BEGIN /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier; + /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapsCallbackApiProtocol.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapsCallbackApiProtocol.h new file mode 100644 index 000000000000..4841dd6e7d9a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/FGMMapsCallbackApiProtocol.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import Foundation; + +#import "google_maps_flutter_pigeon_messages.g.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Protocol for FGMMapsCallbackApi to allow mocking in tests. +/// +/// This is a one-to-one abstraction of the Pigeon-generated API, so that unit tests can inject a +/// fake in place of the real implementation rather than asserting on Pigeon channel internals. +@protocol FGMMapsCallbackApiProtocol + +/// Called when the map camera starts moving. +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera moves. +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera stops moving. +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is tapped. +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is long pressed. +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker is tapped. +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag starts. +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag updates. +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag ends. +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker's info window is tapped. +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a circle is tapped. +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker cluster is tapped. +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polygon is tapped. +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polyline is tapped. +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a ground overlay is tapped. +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called to get data for a map tile. +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.h index 053f0f577650..1db7c8122b19 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/include/google_maps_flutter_ios_sdk10/google_maps_flutter_pigeon_messages.g.h @@ -910,6 +910,9 @@ extern void SetUpFGMMapsApiWithSuffix(id binaryMessenger /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId completion:(void (^)(FlutterError *_Nullable))completion; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeId + completion:(void (^)(FlutterError *_Nullable))completion; /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/google_maps_flutter_ios.dart index c0c8db027c8d..157bce4cbaf6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/google_maps_flutter_ios.dart @@ -182,6 +182,11 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return _events(mapId).whereType(); @@ -989,6 +994,11 @@ class HostMapMessageHandler implements MapsCallbackApi { streamController.add(CircleTapEvent(mapId, CircleId(circleId))); } + @override + void onPointOfInterestTap(String placeId) { + streamController.add(PointOfInterestTapEvent(mapId, PointOfInterestId(placeId))); + } + @override void onClusterTap(PlatformCluster cluster) { streamController.add( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/messages.g.dart index be134f058b12..db2d58fe4335 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/lib/src/messages.g.dart @@ -3355,6 +3355,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); @@ -3734,6 +3737,39 @@ abstract class MapsCallbackApi { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null.', + ); + final List args = (message as List?)!; + final String? arg_placeId = (args[0] as String?); + assert( + arg_placeId != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null, expected non-null String.', + ); + try { + api.onPointOfInterestTap(arg_placeId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onClusterTap$messageChannelSuffix', diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pigeons/messages.dart index 51d82926642f..cf87d51dc84a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pigeons/messages.dart @@ -832,6 +832,10 @@ abstract class MapsCallbackApi { @ObjCSelector('didTapCircleWithIdentifier:') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @ObjCSelector('didTapPointOfInterestWithPlaceIdentifier:') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @ObjCSelector('didTapCluster:') void onClusterTap(PlatformCluster cluster); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pubspec.yaml index f50b68394747..5d3018c8afa9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios_sdk10 description: iOS implementation of the google_maps_flutter plugin using Google Maps SDK 10. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios_sdk10 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.18.5 +version: 2.19.0 environment: sdk: ^3.10.0 @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.14.2 + google_maps_flutter_platform_interface: ^2.17.0 stream_transform: ^2.0.0 dev_dependencies: @@ -37,3 +37,7 @@ topics: - google-maps - google-maps-flutter - map +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/test/google_maps_flutter_ios_test.dart index da996d1b8e6e..410ddd412e85 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/test/google_maps_flutter_ios_test.dart @@ -905,6 +905,20 @@ void main() { expect((await stream.next).value.value, equals(objectId)); }); + test('points of interest send tap events to correct stream', () async { + const mapId = 1; + const placeId = 'place-123'; + + final maps = GoogleMapsFlutterIOS(); + final HostMapMessageHandler callbackHandler = maps.ensureHandlerInitialized(mapId); + + final stream = StreamQueue(maps.onPointOfInterestTap(mapId: mapId)); + + callbackHandler.onPointOfInterestTap(placeId); + + expect((await stream.next).value.value, equals(placeId)); + }); + test('clusters send tap events to correct stream', () async { const mapId = 1; const managerId = 'manager-id'; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/CHANGELOG.md index b8e989f8c9b0..3205f369e902 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.19.0 + +* Adds support for tapping points of interest on the map. + ## 2.18.6 * Fixes a potential compilation issue in tile downscaling. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/GoogleMapsTests.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/GoogleMapsTests.swift index 86be81902dfb..0f1ce5d8328c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/GoogleMapsTests.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/GoogleMapsTests.swift @@ -38,6 +38,98 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Fake implementation of FGMMapsCallbackApiProtocol that records the calls it receives. +class MockMapsCallbackApi: NSObject, FGMMapsCallbackApiProtocol { + var lastTappedPointOfInterestPlaceIdentifier: String? + + func didTapPointOfInterest( + withPlaceIdentifier placeIdentifier: String, + completion: @escaping (FlutterError?) -> Void + ) { + lastTappedPointOfInterestPlaceIdentifier = placeIdentifier + completion(nil) + } + + func didStartCameraMove(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didMoveCamera( + to cameraPosition: FGMPlatformCameraPosition, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didIdleCamera(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didTap( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didLongPress( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didStartDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didDragMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didEndDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapInfoWindowOfMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapCircle( + withIdentifier circleId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTap( + _ cluster: FGMPlatformCluster, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolygon( + withIdentifier polygonId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolyline( + withIdentifier polylineId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapGroundOverlay( + withIdentifier groundOverlayId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func tile( + withOverlayIdentifier tileOverlayId: String, + location: FGMPlatformPoint, + zoom: Int, + completion: @escaping (FGMPlatformTile?, FlutterError?) -> Void + ) { completion(nil, nil) } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -74,7 +166,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) for _ in 0..<10 { @@ -133,7 +226,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -165,7 +259,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -188,6 +283,34 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == durationMilliseconds.doubleValue / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() { + let frame = CGRect(x: 0, y: 0, width: 100, height: 100) + let mapViewOptions = GMSMapViewOptions() + mapViewOptions.frame = frame + mapViewOptions.camera = GMSCameraPosition(latitude: 0, longitude: 0, zoom: 0) + + let mapView = PartiallyMockedMapView(options: mapViewOptions) + + let callbackApi = MockMapsCallbackApi() + let controller = FGMGoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: callbackApi + ) + + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + + #expect(callbackApi.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() @@ -205,7 +328,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: binaryMessenger + binaryMessenger: binaryMessenger, + callbackHandler: MockMapsCallbackApi() ) let inspector = FGMMapInspector( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift index 2c8fa8248be6..cdd7f81d79a4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift @@ -32,6 +32,8 @@ class TestMapEventHandler: NSObject, FGMMapEventDelegate { func didTapCircle(withIdentifier circleId: String) {} + func didTapPointOfInterest(withPlaceIdentifier placeIdentifier: String) {} + func didTap(_ cluster: FGMPlatformCluster) {} func didTapPolygon(withIdentifier polygonId: String) {} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/pubspec.yaml index d041adc00616..3131c3b49c94 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/pubspec.yaml @@ -31,3 +31,7 @@ flutter: uses-material-design: true assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/test/fake_google_maps_flutter_platform.dart index 3e8beefc78c4..f4bc1fd82c8f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/example/test/fake_google_maps_flutter_platform.dart @@ -206,6 +206,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/FGMGoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/FGMGoogleMapController.m index 5f609c9b00de..97aa8651f75a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/FGMGoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/FGMGoogleMapController.m @@ -11,6 +11,7 @@ #import "FGMConversionUtils.h" #import "FGMGroundOverlayController.h" #import "FGMHeatmapController.h" +#import "FGMMapsCallbackApiProtocol.h" #import "FGMMarkerUserData.h" #import "FGMTileOverlayController.h" #import "google_maps_flutter_pigeon_messages.g.h" @@ -99,17 +100,138 @@ - (UIImage *)imageNamed:(NSString *)name { #pragma mark - +/// Non-test implementation of FGMMapsCallbackApiProtocol, passing calls through to a +/// FGMMapsCallbackApi instance. +@interface FGMDefaultMapsCallbackApi : NSObject +@property(strong, nonatomic) FGMMapsCallbackApi *callbackApi; + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix; +@end + +@implementation FGMDefaultMapsCallbackApi + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix { + self = [super init]; + if (self) { + _callbackApi = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger + messageChannelSuffix:messageChannelSuffix]; + } + return self; +} + +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartCameraMoveWithCompletion:completion]; +} + +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didMoveCameraToPosition:cameraPosition completion:completion]; +} + +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didIdleCameraWithCompletion:completion]; +} + +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapAtPosition:position completion:completion]; +} + +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didLongPressAtPosition:position completion:completion]; +} + +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didDragMarkerWithIdentifier:markerId atPosition:position completion:completion]; +} + +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didEndDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapInfoWindowOfMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCircleWithIdentifier:circleId completion:completion]; +} + +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPointOfInterestWithPlaceIdentifier:placeIdentifier completion:completion]; +} + +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCluster:cluster completion:completion]; +} + +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolygonWithIdentifier:polygonId completion:completion]; +} + +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolylineWithIdentifier:polylineId completion:completion]; +} + +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapGroundOverlayWithIdentifier:groundOverlayId completion:completion]; +} + +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion { + [self.callbackApi tileWithOverlayIdentifier:tileOverlayId + location:location + zoom:zoom + completion:completion]; +} + +@end + +#pragma mark - + /// Non-test implementation of FGMAssetProvider, wrapping a FGMMapsCallbackApi /// instance. @interface FGMDefaultMapEventHandler : NSObject -@property(strong, nonatomic) FGMMapsCallbackApi *callbackHandler; +@property(strong, nonatomic) id callbackHandler; -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler; +- (instancetype)initWithCallbackHandler:(id)callbackHandler; @end @implementation FGMDefaultMapEventHandler -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler { +- (instancetype)initWithCallbackHandler:(id)callbackHandler { self = [super init]; if (self) { _callbackHandler = callbackHandler; @@ -186,6 +308,12 @@ - (void)didTapCircleWithIdentifier:(NSString *)circleId { }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier { + [self.callbackHandler didTapPointOfInterestWithPlaceIdentifier:placeIdentifier + completion:^(FlutterError *_){ + }]; +} + - (void)didTapCluster:(FGMPlatformCluster *)cluster { [self.callbackHandler didTapCluster:cluster completion:^(FlutterError *_){ @@ -246,7 +374,7 @@ @interface FGMMapInspector () @interface FGMGoogleMapController () @property(nonatomic, strong) GMSMapView *mapView; -@property(nonatomic, strong) FGMMapsCallbackApi *dartCallbackHandler; +@property(nonatomic, strong) id dartCallbackHandler; @property(nonatomic, strong) FGMDefaultMapEventHandler *mapEventHandler; @property(nonatomic, assign) BOOL trackCameraPosition; @property(nonatomic, strong) FGMClusterManagersController *clusterManagersController; @@ -290,18 +418,23 @@ - (instancetype)initWithFrame:(CGRect)frame GMSMapView *mapView = [[GMSMapView alloc] initWithOptions:options]; + NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; return [self initWithMapView:mapView viewIdentifier:viewId creationParameters:creationParameters assetProvider:[[FGMDefaultAssetProvider alloc] initWithRegistrar:registrar] - binaryMessenger:registrar.messenger]; + binaryMessenger:registrar.messenger + callbackHandler:[[FGMDefaultMapsCallbackApi alloc] + initWithBinaryMessenger:registrar.messenger + messageChannelSuffix:pigeonSuffix]]; } - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger { + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler { if (self = [super init]) { _mapView = mapView; @@ -310,8 +443,7 @@ - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView // https://github.com/flutter/flutter/issues/104121 [self interpretMapConfiguration:creationParameters.mapConfiguration]; NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; - _dartCallbackHandler = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger - messageChannelSuffix:pigeonSuffix]; + _dartCallbackHandler = callbackHandler; _mapEventHandler = [[FGMDefaultMapEventHandler alloc] initWithCallbackHandler:_dartCallbackHandler]; FGMPlatformMarkerType markerType = creationParameters.mapConfiguration.markerType; @@ -558,6 +690,13 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { } } +- (void)mapView:(GMSMapView *)mapView + didTapPOIWithPlaceID:(NSString *)placeID + name:(NSString *)name + location:(CLLocationCoordinate2D)location { + [self.mapEventHandler didTapPointOfInterestWithPlaceIdentifier:placeID]; +} + - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.mapEventHandler didTapAtPosition:FGMGetPigeonLatLngForCoordinate(coordinate)]; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.m b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.m index 3302fd9e13b8..1ebe8dd250be 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.m @@ -2336,11 +2336,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updatePolylinesByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updatePolylinesByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updatePolylinesByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updatePolylinesByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2367,11 +2367,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateTileOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateTileOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2398,11 +2398,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateGroundOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateGroundOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2631,11 +2631,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier: - error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_markerId = GetNullableObjectAtIndex(args, 0); @@ -3068,6 +3068,32 @@ - (void)didTapCircleWithIdentifier:(NSString *)arg_circleId } }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)arg_placeId + completion:(void (^)(FlutterError *_Nullable))completion { + NSString *channelName = [NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap", + _messageChannelSuffix]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; + [channel sendMessage:@[ arg_placeId ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion(createConnectionError(channelName)); + } + }]; +} - (void)didTapCluster:(FGMPlatformCluster *)arg_cluster completion:(void (^)(FlutterError *_Nullable))completion { NSString *channelName = [NSString diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMGoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMGoogleMapController_Test.h index 5e1fa9371b2c..67a27b1433fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMGoogleMapController_Test.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMGoogleMapController_Test.h @@ -8,6 +8,7 @@ #import "FGMAssetProvider.h" #import "FGMCATransactionWrapper.h" #import "FGMGoogleMapController.h" +#import "FGMMapsCallbackApiProtocol.h" NS_ASSUME_NONNULL_BEGIN @@ -48,11 +49,13 @@ NS_ASSUME_NONNULL_BEGIN /// @param creationParameters Parameters for initialising the map view. /// @param assetProvider The asset provider to use for looking up assets. /// @param binaryMessenger The binary messenger to use for sending messages to Dart. +/// @param callbackHandler The callback API to use for sending events to Dart. - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger; + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler; // The main Pigeon API implementation. @property(nonatomic, strong, readonly) FGMMapCallHandler *callHandler; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapEventDelegate.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapEventDelegate.h index f93f1adef252..ee6f9ee5db38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapEventDelegate.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapEventDelegate.h @@ -50,6 +50,9 @@ NS_ASSUME_NONNULL_BEGIN /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier; + /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapsCallbackApiProtocol.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapsCallbackApiProtocol.h new file mode 100644 index 000000000000..4841dd6e7d9a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/FGMMapsCallbackApiProtocol.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import Foundation; + +#import "google_maps_flutter_pigeon_messages.g.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Protocol for FGMMapsCallbackApi to allow mocking in tests. +/// +/// This is a one-to-one abstraction of the Pigeon-generated API, so that unit tests can inject a +/// fake in place of the real implementation rather than asserting on Pigeon channel internals. +@protocol FGMMapsCallbackApiProtocol + +/// Called when the map camera starts moving. +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera moves. +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera stops moving. +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is tapped. +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is long pressed. +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker is tapped. +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag starts. +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag updates. +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag ends. +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker's info window is tapped. +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a circle is tapped. +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker cluster is tapped. +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polygon is tapped. +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polyline is tapped. +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a ground overlay is tapped. +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called to get data for a map tile. +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.h b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.h index 053f0f577650..1db7c8122b19 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/include/google_maps_flutter_ios_sdk9/google_maps_flutter_pigeon_messages.g.h @@ -910,6 +910,9 @@ extern void SetUpFGMMapsApiWithSuffix(id binaryMessenger /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId completion:(void (^)(FlutterError *_Nullable))completion; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeId + completion:(void (^)(FlutterError *_Nullable))completion; /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/google_maps_flutter_ios.dart index c0c8db027c8d..157bce4cbaf6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/google_maps_flutter_ios.dart @@ -182,6 +182,11 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return _events(mapId).whereType(); @@ -989,6 +994,11 @@ class HostMapMessageHandler implements MapsCallbackApi { streamController.add(CircleTapEvent(mapId, CircleId(circleId))); } + @override + void onPointOfInterestTap(String placeId) { + streamController.add(PointOfInterestTapEvent(mapId, PointOfInterestId(placeId))); + } + @override void onClusterTap(PlatformCluster cluster) { streamController.add( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/messages.g.dart index be134f058b12..db2d58fe4335 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/lib/src/messages.g.dart @@ -3355,6 +3355,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); @@ -3734,6 +3737,39 @@ abstract class MapsCallbackApi { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null.', + ); + final List args = (message as List?)!; + final String? arg_placeId = (args[0] as String?); + assert( + arg_placeId != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null, expected non-null String.', + ); + try { + api.onPointOfInterestTap(arg_placeId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onClusterTap$messageChannelSuffix', diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pigeons/messages.dart index 3c69cc451040..eb0890fa8b60 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pigeons/messages.dart @@ -832,6 +832,10 @@ abstract class MapsCallbackApi { @ObjCSelector('didTapCircleWithIdentifier:') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @ObjCSelector('didTapPointOfInterestWithPlaceIdentifier:') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @ObjCSelector('didTapCluster:') void onClusterTap(PlatformCluster cluster); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pubspec.yaml index cbfc993524f7..37ab7f5295e9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios_sdk9 description: iOS implementation of the google_maps_flutter plugin using Google Maps SDK 9. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios_sdk9 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.18.6 +version: 2.19.0 environment: sdk: ^3.10.0 @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.14.2 + google_maps_flutter_platform_interface: ^2.17.0 stream_transform: ^2.0.0 dev_dependencies: @@ -37,3 +37,7 @@ topics: - google-maps - google-maps-flutter - map +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/test/google_maps_flutter_ios_test.dart index da996d1b8e6e..410ddd412e85 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/test/google_maps_flutter_ios_test.dart @@ -905,6 +905,20 @@ void main() { expect((await stream.next).value.value, equals(objectId)); }); + test('points of interest send tap events to correct stream', () async { + const mapId = 1; + const placeId = 'place-123'; + + final maps = GoogleMapsFlutterIOS(); + final HostMapMessageHandler callbackHandler = maps.ensureHandlerInitialized(mapId); + + final stream = StreamQueue(maps.onPointOfInterestTap(mapId: mapId)); + + callbackHandler.onPointOfInterestTap(placeId); + + expect((await stream.next).value.value, equals(placeId)); + }); + test('clusters send tap events to correct stream', () async { const mapId = 1; const managerId = 'manager-id'; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/GoogleMapsTests.swift b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/GoogleMapsTests.swift index bcf512afe221..a95e7a2830a5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/GoogleMapsTests.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/GoogleMapsTests.swift @@ -38,6 +38,98 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Fake implementation of FGMMapsCallbackApiProtocol that records the calls it receives. +class MockMapsCallbackApi: NSObject, FGMMapsCallbackApiProtocol { + var lastTappedPointOfInterestPlaceIdentifier: String? + + func didTapPointOfInterest( + withPlaceIdentifier placeIdentifier: String, + completion: @escaping (FlutterError?) -> Void + ) { + lastTappedPointOfInterestPlaceIdentifier = placeIdentifier + completion(nil) + } + + func didStartCameraMove(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didMoveCamera( + to cameraPosition: FGMPlatformCameraPosition, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didIdleCamera(completion: @escaping (FlutterError?) -> Void) { completion(nil) } + + func didTap( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didLongPress( + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didStartDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didDragMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didEndDragForMarker( + withIdentifier markerId: String, + atPosition position: FGMPlatformLatLng, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapInfoWindowOfMarker( + withIdentifier markerId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapCircle( + withIdentifier circleId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTap( + _ cluster: FGMPlatformCluster, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolygon( + withIdentifier polygonId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapPolyline( + withIdentifier polylineId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func didTapGroundOverlay( + withIdentifier groundOverlayId: String, + completion: @escaping (FlutterError?) -> Void + ) { completion(nil) } + + func tile( + withOverlayIdentifier tileOverlayId: String, + location: FGMPlatformPoint, + zoom: Int, + completion: @escaping (FGMPlatformTile?, FlutterError?) -> Void + ) { completion(nil, nil) } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -74,7 +166,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) for _ in 0..<10 { @@ -133,7 +226,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -165,7 +259,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: StubBinaryMessenger() + binaryMessenger: StubBinaryMessenger(), + callbackHandler: MockMapsCallbackApi() ) let mockTransactionWrapper = MockCATransaction() @@ -188,6 +283,34 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == durationMilliseconds.doubleValue / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() { + let frame = CGRect(x: 0, y: 0, width: 100, height: 100) + let mapViewOptions = GMSMapViewOptions() + mapViewOptions.frame = frame + mapViewOptions.camera = GMSCameraPosition(latitude: 0, longitude: 0, zoom: 0) + + let mapView = PartiallyMockedMapView(options: mapViewOptions) + + let callbackApi = MockMapsCallbackApi() + let controller = FGMGoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: callbackApi + ) + + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + + #expect(callbackApi.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() @@ -205,7 +328,8 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { viewIdentifier: 0, creationParameters: emptyCreationParameters(), assetProvider: TestAssetProvider(), - binaryMessenger: binaryMessenger + binaryMessenger: binaryMessenger, + callbackHandler: MockMapsCallbackApi() ) let inspector = FGMMapInspector( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift index 121738f6c540..4fe0daa2f3a1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/ios/RunnerTests/TestUtils/TestMapEventHandler.swift @@ -32,6 +32,8 @@ class TestMapEventHandler: NSObject, FGMMapEventDelegate { func didTapCircle(withIdentifier circleId: String) {} + func didTapPointOfInterest(withPlaceIdentifier placeIdentifier: String) {} + func didTap(_ cluster: FGMPlatformCluster) {} func didTapPolygon(withIdentifier polygonId: String) {} diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/test/fake_google_maps_flutter_platform.dart index 3e8beefc78c4..f4bc1fd82c8f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/example/test/fake_google_maps_flutter_platform.dart @@ -206,6 +206,11 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { return mapEventStreamController.stream.whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m index 5f609c9b00de..97aa8651f75a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/FGMGoogleMapController.m @@ -11,6 +11,7 @@ #import "FGMConversionUtils.h" #import "FGMGroundOverlayController.h" #import "FGMHeatmapController.h" +#import "FGMMapsCallbackApiProtocol.h" #import "FGMMarkerUserData.h" #import "FGMTileOverlayController.h" #import "google_maps_flutter_pigeon_messages.g.h" @@ -99,17 +100,138 @@ - (UIImage *)imageNamed:(NSString *)name { #pragma mark - +/// Non-test implementation of FGMMapsCallbackApiProtocol, passing calls through to a +/// FGMMapsCallbackApi instance. +@interface FGMDefaultMapsCallbackApi : NSObject +@property(strong, nonatomic) FGMMapsCallbackApi *callbackApi; + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix; +@end + +@implementation FGMDefaultMapsCallbackApi + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger + messageChannelSuffix:(NSString *)messageChannelSuffix { + self = [super init]; + if (self) { + _callbackApi = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger + messageChannelSuffix:messageChannelSuffix]; + } + return self; +} + +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartCameraMoveWithCompletion:completion]; +} + +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didMoveCameraToPosition:cameraPosition completion:completion]; +} + +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didIdleCameraWithCompletion:completion]; +} + +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapAtPosition:position completion:completion]; +} + +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didLongPressAtPosition:position completion:completion]; +} + +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didStartDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didDragMarkerWithIdentifier:markerId atPosition:position completion:completion]; +} + +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didEndDragForMarkerWithIdentifier:markerId + atPosition:position + completion:completion]; +} + +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapInfoWindowOfMarkerWithIdentifier:markerId completion:completion]; +} + +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCircleWithIdentifier:circleId completion:completion]; +} + +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPointOfInterestWithPlaceIdentifier:placeIdentifier completion:completion]; +} + +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapCluster:cluster completion:completion]; +} + +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolygonWithIdentifier:polygonId completion:completion]; +} + +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapPolylineWithIdentifier:polylineId completion:completion]; +} + +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion { + [self.callbackApi didTapGroundOverlayWithIdentifier:groundOverlayId completion:completion]; +} + +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion { + [self.callbackApi tileWithOverlayIdentifier:tileOverlayId + location:location + zoom:zoom + completion:completion]; +} + +@end + +#pragma mark - + /// Non-test implementation of FGMAssetProvider, wrapping a FGMMapsCallbackApi /// instance. @interface FGMDefaultMapEventHandler : NSObject -@property(strong, nonatomic) FGMMapsCallbackApi *callbackHandler; +@property(strong, nonatomic) id callbackHandler; -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler; +- (instancetype)initWithCallbackHandler:(id)callbackHandler; @end @implementation FGMDefaultMapEventHandler -- (instancetype)initWithCallbackHandler:(FGMMapsCallbackApi *)callbackHandler { +- (instancetype)initWithCallbackHandler:(id)callbackHandler { self = [super init]; if (self) { _callbackHandler = callbackHandler; @@ -186,6 +308,12 @@ - (void)didTapCircleWithIdentifier:(NSString *)circleId { }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier { + [self.callbackHandler didTapPointOfInterestWithPlaceIdentifier:placeIdentifier + completion:^(FlutterError *_){ + }]; +} + - (void)didTapCluster:(FGMPlatformCluster *)cluster { [self.callbackHandler didTapCluster:cluster completion:^(FlutterError *_){ @@ -246,7 +374,7 @@ @interface FGMMapInspector () @interface FGMGoogleMapController () @property(nonatomic, strong) GMSMapView *mapView; -@property(nonatomic, strong) FGMMapsCallbackApi *dartCallbackHandler; +@property(nonatomic, strong) id dartCallbackHandler; @property(nonatomic, strong) FGMDefaultMapEventHandler *mapEventHandler; @property(nonatomic, assign) BOOL trackCameraPosition; @property(nonatomic, strong) FGMClusterManagersController *clusterManagersController; @@ -290,18 +418,23 @@ - (instancetype)initWithFrame:(CGRect)frame GMSMapView *mapView = [[GMSMapView alloc] initWithOptions:options]; + NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; return [self initWithMapView:mapView viewIdentifier:viewId creationParameters:creationParameters assetProvider:[[FGMDefaultAssetProvider alloc] initWithRegistrar:registrar] - binaryMessenger:registrar.messenger]; + binaryMessenger:registrar.messenger + callbackHandler:[[FGMDefaultMapsCallbackApi alloc] + initWithBinaryMessenger:registrar.messenger + messageChannelSuffix:pigeonSuffix]]; } - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger { + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler { if (self = [super init]) { _mapView = mapView; @@ -310,8 +443,7 @@ - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView // https://github.com/flutter/flutter/issues/104121 [self interpretMapConfiguration:creationParameters.mapConfiguration]; NSString *pigeonSuffix = [NSString stringWithFormat:@"%lld", viewId]; - _dartCallbackHandler = [[FGMMapsCallbackApi alloc] initWithBinaryMessenger:binaryMessenger - messageChannelSuffix:pigeonSuffix]; + _dartCallbackHandler = callbackHandler; _mapEventHandler = [[FGMDefaultMapEventHandler alloc] initWithCallbackHandler:_dartCallbackHandler]; FGMPlatformMarkerType markerType = creationParameters.mapConfiguration.markerType; @@ -558,6 +690,13 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { } } +- (void)mapView:(GMSMapView *)mapView + didTapPOIWithPlaceID:(NSString *)placeID + name:(NSString *)name + location:(CLLocationCoordinate2D)location { + [self.mapEventHandler didTapPointOfInterestWithPlaceIdentifier:placeID]; +} + - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.mapEventHandler didTapAtPosition:FGMGetPigeonLatLngForCoordinate(coordinate)]; } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m index 3302fd9e13b8..1ebe8dd250be 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.m @@ -2336,11 +2336,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updatePolylinesByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updatePolylinesByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updatePolylinesByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updatePolylinesByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2367,11 +2367,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateTileOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateTileOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateTileOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2398,11 +2398,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(updateGroundOverlaysByAdding: - changing:removing:error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(updateGroundOverlaysByAdding:changing:removing:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(updateGroundOverlaysByAdding:changing:removing:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSArray *arg_toAdd = GetNullableObjectAtIndex(args, 0); @@ -2631,11 +2631,11 @@ void SetUpFGMMapsApiWithSuffix(id binaryMessenger, binaryMessenger:binaryMessenger codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier: - error:)], - @"FGMMapsApi api (%@) doesn't respond to " - @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)], + @"FGMMapsApi api (%@) doesn't respond to " + @"@selector(isShowingInfoWindowForMarkerWithIdentifier:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_markerId = GetNullableObjectAtIndex(args, 0); @@ -3068,6 +3068,32 @@ - (void)didTapCircleWithIdentifier:(NSString *)arg_circleId } }]; } +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)arg_placeId + completion:(void (^)(FlutterError *_Nullable))completion { + NSString *channelName = [NSString + stringWithFormat: + @"%@%@", + @"dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap", + _messageChannelSuffix]; + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:channelName + binaryMessenger:self.binaryMessenger + codec:FGMGetGoogleMapsFlutterPigeonMessagesCodec()]; + [channel sendMessage:@[ arg_placeId ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion(createConnectionError(channelName)); + } + }]; +} - (void)didTapCluster:(FGMPlatformCluster *)arg_cluster completion:(void (^)(FlutterError *_Nullable))completion { NSString *channelName = [NSString diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h index 5e1fa9371b2c..67a27b1433fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMGoogleMapController_Test.h @@ -8,6 +8,7 @@ #import "FGMAssetProvider.h" #import "FGMCATransactionWrapper.h" #import "FGMGoogleMapController.h" +#import "FGMMapsCallbackApiProtocol.h" NS_ASSUME_NONNULL_BEGIN @@ -48,11 +49,13 @@ NS_ASSUME_NONNULL_BEGIN /// @param creationParameters Parameters for initialising the map view. /// @param assetProvider The asset provider to use for looking up assets. /// @param binaryMessenger The binary messenger to use for sending messages to Dart. +/// @param callbackHandler The callback API to use for sending events to Dart. - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId creationParameters:(FGMPlatformMapViewCreationParams *)creationParameters assetProvider:(NSObject *)assetProvider - binaryMessenger:(NSObject *)binaryMessenger; + binaryMessenger:(NSObject *)binaryMessenger + callbackHandler:(id)callbackHandler; // The main Pigeon API implementation. @property(nonatomic, strong, readonly) FGMMapCallHandler *callHandler; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h index f93f1adef252..ee6f9ee5db38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapEventDelegate.h @@ -50,6 +50,9 @@ NS_ASSUME_NONNULL_BEGIN /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier; + /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h new file mode 100644 index 000000000000..4841dd6e7d9a --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/FGMMapsCallbackApiProtocol.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import Foundation; + +#import "google_maps_flutter_pigeon_messages.g.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Protocol for FGMMapsCallbackApi to allow mocking in tests. +/// +/// This is a one-to-one abstraction of the Pigeon-generated API, so that unit tests can inject a +/// fake in place of the real implementation rather than asserting on Pigeon channel internals. +@protocol FGMMapsCallbackApiProtocol + +/// Called when the map camera starts moving. +- (void)didStartCameraMoveWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera moves. +- (void)didMoveCameraToPosition:(FGMPlatformCameraPosition *)cameraPosition + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map camera stops moving. +- (void)didIdleCameraWithCompletion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is tapped. +- (void)didTapAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when the map, not a specifc map object, is long pressed. +- (void)didLongPressAtPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker is tapped. +- (void)didTapMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag starts. +- (void)didStartDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag updates. +- (void)didDragMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker drag ends. +- (void)didEndDragForMarkerWithIdentifier:(NSString *)markerId + atPosition:(FGMPlatformLatLng *)position + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker's info window is tapped. +- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)markerId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a circle is tapped. +- (void)didTapCircleWithIdentifier:(NSString *)circleId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeIdentifier + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a marker cluster is tapped. +- (void)didTapCluster:(FGMPlatformCluster *)cluster + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polygon is tapped. +- (void)didTapPolygonWithIdentifier:(NSString *)polygonId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a polyline is tapped. +- (void)didTapPolylineWithIdentifier:(NSString *)polylineId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called when a ground overlay is tapped. +- (void)didTapGroundOverlayWithIdentifier:(NSString *)groundOverlayId + completion:(void (^)(FlutterError *_Nullable))completion; + +/// Called to get data for a map tile. +- (void)tileWithOverlayIdentifier:(NSString *)tileOverlayId + location:(FGMPlatformPoint *)location + zoom:(NSInteger)zoom + completion:(void (^)(FGMPlatformTile *_Nullable, + FlutterError *_Nullable))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h index 053f0f577650..1db7c8122b19 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/include/google_maps_flutter_ios/google_maps_flutter_pigeon_messages.g.h @@ -910,6 +910,9 @@ extern void SetUpFGMMapsApiWithSuffix(id binaryMessenger /// Called when a circle is tapped. - (void)didTapCircleWithIdentifier:(NSString *)circleId completion:(void (^)(FlutterError *_Nullable))completion; +/// Called when a point of interest is tapped. +- (void)didTapPointOfInterestWithPlaceIdentifier:(NSString *)placeId + completion:(void (^)(FlutterError *_Nullable))completion; /// Called when a marker cluster is tapped. - (void)didTapCluster:(FGMPlatformCluster *)cluster completion:(void (^)(FlutterError *_Nullable))completion; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/google_maps_flutter_ios.dart index c0c8db027c8d..157bce4cbaf6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/google_maps_flutter_ios.dart @@ -182,6 +182,11 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onGroundOverlayTap({required int mapId}) { return _events(mapId).whereType(); @@ -989,6 +994,11 @@ class HostMapMessageHandler implements MapsCallbackApi { streamController.add(CircleTapEvent(mapId, CircleId(circleId))); } + @override + void onPointOfInterestTap(String placeId) { + streamController.add(PointOfInterestTapEvent(mapId, PointOfInterestId(placeId))); + } + @override void onClusterTap(PlatformCluster cluster) { streamController.add( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/messages.g.dart index be134f058b12..db2d58fe4335 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/lib/src/messages.g.dart @@ -3355,6 +3355,9 @@ abstract class MapsCallbackApi { /// Called when a circle is tapped. void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. void onClusterTap(PlatformCluster cluster); @@ -3734,6 +3737,39 @@ abstract class MapsCallbackApi { }); } } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null.', + ); + final List args = (message as List?)!; + final String? arg_placeId = (args[0] as String?); + assert( + arg_placeId != null, + 'Argument for dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap was null, expected non-null String.', + ); + try { + api.onPointOfInterestTap(arg_placeId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onClusterTap$messageChannelSuffix', diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/pigeons/messages.dart index a04e857d05fc..017595dfa6dd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/pigeons/messages.dart @@ -832,6 +832,10 @@ abstract class MapsCallbackApi { @ObjCSelector('didTapCircleWithIdentifier:') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @ObjCSelector('didTapPointOfInterestWithPlaceIdentifier:') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @ObjCSelector('didTapCluster:') void onClusterTap(PlatformCluster cluster); diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/test/google_maps_flutter_ios_test.dart index da996d1b8e6e..410ddd412e85 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/test/google_maps_flutter_ios_test.dart @@ -905,6 +905,20 @@ void main() { expect((await stream.next).value.value, equals(objectId)); }); + test('points of interest send tap events to correct stream', () async { + const mapId = 1; + const placeId = 'place-123'; + + final maps = GoogleMapsFlutterIOS(); + final HostMapMessageHandler callbackHandler = maps.ensureHandlerInitialized(mapId); + + final stream = StreamQueue(maps.onPointOfInterestTap(mapId: mapId)); + + callbackHandler.onPointOfInterestTap(placeId); + + expect((await stream.next).value.value, equals(placeId)); + }); + test('clusters send tap events to correct stream', () async { const mapId = 1; const managerId = 'manager-id'; 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 7adb512a6276..e77c4db5e694 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.17.0 + +* Adds support for tapping points of interest on the map. + ## 2.16.0 * Adds support for `mapTypeControlEnabled`, `fullscreenControlEnabled`, and `streetViewControlEnabled` for web. 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 0d3f47cde019..60d11d177ebc 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 @@ -160,6 +160,15 @@ class GroundOverlayTapEvent extends MapEvent { GroundOverlayTapEvent(super.mapId, super.croundOverlayId); } +/// An event fired when a point of interest is tapped. +class PointOfInterestTapEvent extends MapEvent { + /// Build a PointOfInterestTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [PointOfInterestId] object that represents the + /// tapped point of interest. + PointOfInterestTapEvent(super.mapId, super.pointOfInterestId); +} + /// An event fired when a Map is tapped. class MapTapEvent extends _PositionedMapEvent { /// Build an MapTap Event triggered from the map represented by `mapId`. 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 4ca64f66a8c6..2e8b29fdc238 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 @@ -337,6 +337,11 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('onCircleTap() has not been implemented.'); } + /// A point of interest has been tapped. + Stream onPointOfInterestTap({required int mapId}) { + return const Stream.empty(); + } + /// A Map has been tapped at a certain [LatLng]. Stream onTap({required int mapId}) { throw UnimplementedError('onTap() has not been implemented.'); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/point_of_interest_id.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/point_of_interest_id.dart new file mode 100644 index 000000000000..983a1e917b5e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/point_of_interest_id.dart @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors +// 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 point of interest on a [GoogleMap]. +/// +/// The [value] is the Google Maps place ID for the tapped point of interest. +@immutable +class PointOfInterestId extends MapsObjectId { + /// Creates an immutable identifier for a point of interest. + const PointOfInterestId(super.value); +} 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 80e380674337..673a8406ae6a 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 @@ -28,6 +28,7 @@ export 'maps_object_updates.dart'; export 'marker.dart'; export 'marker_updates.dart'; export 'pattern_item.dart'; +export 'point_of_interest_id.dart'; export 'polygon.dart'; export 'polygon_updates.dart'; export 'polyline.dart'; 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 deb836ce66b5..46dc8a5d52f9 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.16.0 +version: 2.17.0 environment: sdk: ^3.10.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 2254a30780a9..446e1b88eabf 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 @@ -95,6 +95,12 @@ void main() { ); }); + test('onPointOfInterestTap() returns empty stream', () async { + final Stream stream = BuildViewGoogleMapsFlutterPlatform() + .onPointOfInterestTap(mapId: 0); + expect(await stream.isEmpty, isTrue); + }); + test('default implementation of `getStyleError` returns null', () async { final GoogleMapsFlutterPlatform platform = BuildViewGoogleMapsFlutterPlatform(); expect(await platform.getStyleError(mapId: 0), null); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/point_of_interest_id_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/point_of_interest_id_test.dart new file mode 100644 index 000000000000..082ff61877c5 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/point_of_interest_id_test.dart @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors +// 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() { + test('PointOfInterestId equality', () { + const id1 = PointOfInterestId('place-123'); + const id2 = PointOfInterestId('place-123'); + const id3 = PointOfInterestId('place-456'); + + expect(id1, equals(id2)); + expect(id1, isNot(equals(id3))); + expect(id1.hashCode, equals(id2.hashCode)); + }); + + test('PointOfInterestId toString', () { + const id = PointOfInterestId('place-123'); + expect(id.toString(), contains('place-123')); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index adc4d66e9eca..83c06cf8502f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.4 + +* Adds support for tapping points of interest on the map. + ## 0.6.3 * Adds support for mapTypeControlEnabled, fullscreenControlEnabled, and streetViewControlEnabled. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/3-64/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/3-64/pubspec.yaml index 320ad0bf1c51..e6bab1f7cc97 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/3-64/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/3-64/pubspec.yaml @@ -19,3 +19,7 @@ dev_dependencies: google_maps: ^8.1.0 integration_test: sdk: flutter +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_controller_test.dart index 1efafae752fc..6008918c23d9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_controller_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_controller_test.dart @@ -248,6 +248,36 @@ void main() { expect(events[4], isA()); }); + testWidgets('emits point of interest tap when click has placeId', ( + WidgetTester tester, + ) async { + controller = createController() + ..debugSetOverrides( + createMap: (_, _) => map, + circles: circles, + heatmaps: heatmaps, + markers: markers, + polygons: polygons, + polylines: polylines, + groundOverlays: groundOverlays, + ) + ..init(); + + final Stream> capturedEvents = stream.stream.take(1); + + gmaps.event.trigger( + map, + 'click', + gmaps.IconMouseEvent(placeId: 'place-123')..latLng = gmaps.LatLng(0, 0), + ); + + final List> events = await capturedEvents.toList(); + + expect(events, hasLength(1)); + expect(events[0], isA()); + expect((events[0] as PointOfInterestTapEvent).value, const PointOfInterestId('place-123')); + }); + testWidgets('stops listening to map events once disposed', (WidgetTester tester) async { controller = createController() ..debugSetOverrides( diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_plugin_test.dart index ca1cde5334d9..da40728234f6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_plugin_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/integration_test/google_maps_plugin_test.dart @@ -467,6 +467,13 @@ void main() { await testStreamFiltering(stream, event); }); + testWidgets('onPointOfInterestTap', (WidgetTester tester) async { + final event = PointOfInterestTapEvent(mapId, const PointOfInterestId('place-123')); + + final Stream stream = plugin.onPointOfInterestTap(mapId: mapId); + + await testStreamFiltering(stream, event); + }); // Map taps testWidgets('onTap', (WidgetTester tester) async { final event = MapTapEvent(mapId, const LatLng(43.3597, -5.8458)); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/pubspec.yaml index 3c6b3674fd44..2df98a79efc7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/latest/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/latest/pubspec.yaml @@ -28,9 +28,8 @@ flutter: assets: - assets/ +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins dependency_overrides: - # Override the google_maps_flutter dependency on google_maps_flutter_web. - # TODO(ditman): Unwind the circular dependency. This will create problems - # if we need to make a breaking change to google_maps_flutter_web. - google_maps_flutter_web: - path: ../.. + google_maps_flutter_platform_interface: {path: ../../../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} + google_maps_flutter_web: {path: ../..} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index 0413faaf104d..726a0908c839 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -7,6 +7,7 @@ library google_maps_flutter_web; import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'dart:ui_web' as ui_web; import 'package:collection/collection.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 13e9054d9c7c..23bb4f7029b9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -289,6 +289,13 @@ class GoogleMapController { _onClickSubscription = map.onClick.listen((gmaps.MapMouseEventOrIconMouseEvent event) { assert(event.latLng != null); if (!_streamController.isClosed) { + if (event.hasProperty('placeId'.toJS).toDart) { + final String? placeId = (event as gmaps.IconMouseEvent).placeId; + if (placeId != null) { + _streamController.add(PointOfInterestTapEvent(_mapId, PointOfInterestId(placeId))); + return; + } + } _streamController.add(MapTapEvent(_mapId, gmLatLngToLatLng(event.latLng!))); } }); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 9e1ce02285d9..9c21fb1af051 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -244,6 +244,11 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onPointOfInterestTap({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onTap({required int mapId}) { return _events(mapId).whereType(); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 8836876b552a..7baf2c2c19d6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.6.3 +version: 0.6.4 environment: sdk: ^3.10.0 @@ -23,7 +23,7 @@ dependencies: flutter_web_plugins: sdk: flutter google_maps: ^8.1.0 - google_maps_flutter_platform_interface: ^2.16.0 + google_maps_flutter_platform_interface: ^2.17.0 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 web: ^1.0.0 @@ -40,3 +40,7 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /example/**/web/index.html +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + google_maps_flutter_platform_interface: {path: ../../../packages/google_maps_flutter/google_maps_flutter_platform_interface} From 954c841ff216527809fa9be67b92da42f662781f Mon Sep 17 00:00:00 2001 From: tenninebt <5684363+tenninebt@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:15:14 +0200 Subject: [PATCH 2/2] [google_maps_flutter_web] Read POI placeId without dart:js_interop_unsafe MapMouseEventOrIconMouseEvent only exposes latLng upstream, so add a local external placeId getter and use that in the click handler instead of hasProperty + cast. --- .../lib/google_maps_flutter_web.dart | 2 +- .../lib/src/google_maps_controller.dart | 10 ++++----- .../lib/src/map_mouse_event_extension.dart | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_mouse_event_extension.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index 726a0908c839..266bebf47050 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -7,7 +7,6 @@ library google_maps_flutter_web; import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'dart:ui_web' as ui_web; import 'package:collection/collection.dart'; @@ -25,6 +24,7 @@ import 'package:web/web.dart' as web; import 'src/dom_window_extension.dart'; import 'src/google_maps_inspector_web.dart'; +import 'src/map_mouse_event_extension.dart'; import 'src/map_styler.dart'; import 'src/marker_clustering.dart'; import 'src/third_party/to_screen_location/to_screen_location.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index 23bb4f7029b9..dd11bb2d7c65 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -289,12 +289,10 @@ class GoogleMapController { _onClickSubscription = map.onClick.listen((gmaps.MapMouseEventOrIconMouseEvent event) { assert(event.latLng != null); if (!_streamController.isClosed) { - if (event.hasProperty('placeId'.toJS).toDart) { - final String? placeId = (event as gmaps.IconMouseEvent).placeId; - if (placeId != null) { - _streamController.add(PointOfInterestTapEvent(_mapId, PointOfInterestId(placeId))); - return; - } + final String? placeId = event.placeId; + if (placeId != null) { + _streamController.add(PointOfInterestTapEvent(_mapId, PointOfInterestId(placeId))); + return; } _streamController.add(MapTapEvent(_mapId, gmLatLngToLatLng(event.latLng!))); } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_mouse_event_extension.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_mouse_event_extension.dart new file mode 100644 index 000000000000..870972a9c83e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/map_mouse_event_extension.dart @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@JS() +library; + +import 'dart:js_interop'; + +import 'package:google_maps/google_maps.dart' as gmaps; + +/// Exposes the `placeId` property on map click events. +/// +/// [gmaps.MapMouseEventOrIconMouseEvent] only binds `latLng` upstream. POI +/// clicks are [gmaps.IconMouseEvent]s and carry a `placeId`; this extension +/// reads that property without `dart:js_interop_unsafe`. Prefer adding +/// `placeId` to the upstream binding when possible. +extension PlaceIdExtension on gmaps.MapMouseEventOrIconMouseEvent { + /// The place ID of a tapped point of interest, if this event is an icon + /// mouse event. Otherwise `null`. + external String? placeId; +}