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 5152eb02f1a7..b12bfe1b6aad 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.20.0 +* Adds support for tapping points of interest on the map. * Updates minimum supported SDK version to Flutter 3.41/Dart 3.11. ## 2.19.13 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 71c270f71682..c1b95e5c1588 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 @@ -4247,6 +4247,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 b3f4329cc259..2f7c537f82e6 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 @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - google_maps_flutter_platform_interface: ^2.13.0 + google_maps_flutter_platform_interface: ^2.17.0 dev_dependencies: build_runner: ^2.1.10 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 7c66f0601f3c..269946503c9c 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 @@ -3656,6 +3656,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); @@ -3951,6 +3954,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 7a6b7d395c07..ed87d07f165e 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.13 +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 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_sdk10/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/CHANGELOG.md index af9f91c85fc9..ab988e410633 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,5 +1,6 @@ -## NEXT +## 2.19.0 +* Adds support for tapping points of interest on the map. * Updates minimum supported SDK version to Flutter 3.41/Dart 3.11. ## 2.18.13 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 2a0b6cebfa84..2b5662e45f05 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,27 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Records POI tap callbacks for unit tests. +class MockMapEventHandler: TestMapEventHandler { + var lastTappedPointOfInterestPlaceIdentifier: String? + private var pointOfInterestTapContinuation: CheckedContinuation? + + func waitForPointOfInterestTap() async { + if lastTappedPointOfInterestPlaceIdentifier != nil { + return + } + await withCheckedContinuation { continuation in + pointOfInterestTapContinuation = continuation + } + } + + override func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + lastTappedPointOfInterestPlaceIdentifier = placeIdArg + pointOfInterestTapContinuation?.resume() + pointOfInterestTapContinuation = nil + } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -167,6 +188,37 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == Double(durationMilliseconds) / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() async { + 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 mapEventHandler = MockMapEventHandler() + let controller = GoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: mapEventHandler, + tileProvider: mapEventHandler + ) + + async let poiTapReceived: Void = mapEventHandler.waitForPointOfInterestTap() + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + await poiTapReceived + + #expect(mapEventHandler.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() 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 83e76fdf5b80..fb49a27c8f74 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 @@ -6,8 +6,8 @@ import Foundation @testable import google_maps_flutter_ios_sdk10 -/// Fake implementation of MapEventDelegate for unit tests. -class TestMapEventHandler: MapEventDelegate { +/// Fake implementation of MapEventDelegate (and TileProviderDelegate) for unit tests. +class TestMapEventHandler: MapEventDelegate, TileProviderDelegate { func didStartCameraMove() async throws {} func didMoveCamera(to cameraPositionArg: PlatformCameraPosition) async throws {} @@ -39,6 +39,8 @@ class TestMapEventHandler: MapEventDelegate { func didTapCircle(withIdentifier circleIdArg: String) async throws {} + func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws {} + func didTapCluster(_ clusterArg: PlatformCluster) async throws {} func didTapPolygon(withIdentifier polygonIdArg: String) async throws {} 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 05bbfd851828..6e3676c47db3 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 @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - google_maps_flutter_platform_interface: ^2.14.1 + google_maps_flutter_platform_interface: ^2.17.0 dev_dependencies: flutter_test: 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/GoogleMapController.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/GoogleMapController.swift index cc99b83f3ccd..6e023617ce5f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/GoogleMapController.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/GoogleMapController.swift @@ -59,7 +59,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV /// The Google Maps SDK map view managed by this controller. let mapView: GMSMapView /// The Pigeon callback API implementation, used to send events to the Dart side. - let dartCallbackHandler: MapsCallbackApi + let dartCallbackHandler: MapEventDelegate /// The main Pigeon API implementation, separate to avoid lifetime extension. let callHandler: MapCallHandler /// The inspector API implementation, separate to avoid lifetime extension. @@ -101,21 +101,52 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV options.mapID = GMSMapID(identifier: mapId) } + let binaryMessenger = registrar.messenger() + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) self.init( mapView: GMSMapView(options: options), viewIdentifier: viewId, creationParameters: creationParameters, assetProvider: DefaultAssetProvider(registrar: registrar), - binaryMessenger: registrar.messenger() + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler ) } - init( + convenience init( mapView: GMSMapView, viewIdentifier viewId: Int64, creationParameters: PlatformMapViewCreationParams, assetProvider: AssetProvider, binaryMessenger: FlutterBinaryMessenger + ) { + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) + self.init( + mapView: mapView, + viewIdentifier: viewId, + creationParameters: creationParameters, + assetProvider: assetProvider, + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler + ) + } + + init( + mapView: GMSMapView, + viewIdentifier viewId: Int64, + creationParameters: PlatformMapViewCreationParams, + assetProvider: AssetProvider, + binaryMessenger: FlutterBinaryMessenger, + callbackHandler: MapEventDelegate, + tileProvider: TileProviderDelegate ) { self.mapView = mapView mapView.accessibilityElementsHidden = false @@ -134,10 +165,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV // End duplicate code. let pigeonSuffix = String(format: "%lld", viewId) - dartCallbackHandler = MapsCallbackApi( - binaryMessenger: binaryMessenger, - messageChannelSuffix: pigeonSuffix - ) + dartCallbackHandler = callbackHandler let markerType = creationParameters.mapConfiguration.markerType @@ -167,7 +195,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV heatmapsController = HeatmapsController(mapView: mapView) tileOverlaysController = TileOverlaysController( mapView: mapView, - tileProvider: dartCallbackHandler + tileProvider: tileProvider ) groundOverlaysController = GroundOverlaysController( mapView: mapView, @@ -396,6 +424,15 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV } } + public func mapView( + _ mapView: GMSMapView, didTapPOIWithPlaceID placeID: String, name: String, + location: CLLocationCoordinate2D + ) { + Task { + try await dartCallbackHandler.didTapPointOfInterest(withPlaceIdentifier: placeID) + } + } + func interpretMapConfiguration(_ config: PlatformMapConfiguration) { // Any changes here must also be made to the `init` method above. See the comment there for // details. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/Messages.g.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/Messages.g.swift index 3689467b7fea..0439edf54b0e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/Messages.g.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk10/ios/google_maps_flutter_ios_sdk10/Sources/google_maps_flutter_ios_sdk10/Messages.g.swift @@ -3455,6 +3455,8 @@ protocol MapsCallbackApiProtocol { @MainActor func didTapInfoWindowOfMarker(withIdentifier markerIdArg: String) async throws /// Called when a circle is tapped. @MainActor func didTapCircle(withIdentifier circleIdArg: String) async throws + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws /// Called when a polygon is tapped. @@ -3738,6 +3740,29 @@ class MapsCallbackApi: MapsCallbackApiProtocol { } } } + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + return try await withCheckedThrowingContinuation { continuation in + let channelName: String = + "dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([placeIdArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + continuation.resume(throwing: createConnectionError(withChannelName: channelName)) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + continuation.resume(throwing: PigeonError(code: code, message: message, details: details)) + } else { + continuation.resume() + } + } + } + } /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws { return try await withCheckedThrowingContinuation { continuation in 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 11df18180a50..252912f722cd 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(); @@ -957,6 +962,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 d5c89d021360..76c32ea35680 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 @@ -3456,6 +3456,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); @@ -3751,6 +3754,31 @@ 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 { + 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_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 3f620b692269..673502373e3a 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 @@ -803,6 +803,10 @@ abstract class MapsCallbackApi { @SwiftFunction('didTapCircle(withIdentifier:)') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @SwiftFunction('didTapPointOfInterest(withPlaceIdentifier:)') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @SwiftFunction('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 0f8a01fd8ffc..eda51a594487 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.13 +version: 2.19.0 environment: sdk: ^3.11.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 meta: ^1.10.0 stream_transform: ^2.0.0 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 6d21a3e81c1f..978a022a4194 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 @@ -903,6 +903,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-id'; + + 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 2a12f16cf1db..dd2d62161fe7 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,5 +1,6 @@ -## NEXT +## 2.19.0 +* Adds support for tapping points of interest on the map. * Updates minimum supported SDK version to Flutter 3.41/Dart 3.11. ## 2.18.14 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 189a00f20440..e52c3c3afe6e 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,27 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Records POI tap callbacks for unit tests. +class MockMapEventHandler: TestMapEventHandler { + var lastTappedPointOfInterestPlaceIdentifier: String? + private var pointOfInterestTapContinuation: CheckedContinuation? + + func waitForPointOfInterestTap() async { + if lastTappedPointOfInterestPlaceIdentifier != nil { + return + } + await withCheckedContinuation { continuation in + pointOfInterestTapContinuation = continuation + } + } + + override func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + lastTappedPointOfInterestPlaceIdentifier = placeIdArg + pointOfInterestTapContinuation?.resume() + pointOfInterestTapContinuation = nil + } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -167,6 +188,37 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == Double(durationMilliseconds) / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() async { + 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 mapEventHandler = MockMapEventHandler() + let controller = GoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: mapEventHandler, + tileProvider: mapEventHandler + ) + + async let poiTapReceived: Void = mapEventHandler.waitForPointOfInterestTap() + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + await poiTapReceived + + #expect(mapEventHandler.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() 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 2c72dffe0570..e76c32ab01a9 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 @@ -6,8 +6,8 @@ import Foundation @testable import google_maps_flutter_ios_sdk9 -/// Fake implementation of MapEventDelegate for unit tests. -class TestMapEventHandler: MapEventDelegate { +/// Fake implementation of MapEventDelegate (and TileProviderDelegate) for unit tests. +class TestMapEventHandler: MapEventDelegate, TileProviderDelegate { func didStartCameraMove() async throws {} func didMoveCamera(to cameraPositionArg: PlatformCameraPosition) async throws {} @@ -39,6 +39,8 @@ class TestMapEventHandler: MapEventDelegate { func didTapCircle(withIdentifier circleIdArg: String) async throws {} + func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws {} + func didTapCluster(_ clusterArg: PlatformCluster) async throws {} func didTapPolygon(withIdentifier polygonIdArg: String) async throws {} 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 2009c6b74765..94a5b02efa8c 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 @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - google_maps_flutter_platform_interface: ^2.14.1 + google_maps_flutter_platform_interface: ^2.17.0 dev_dependencies: flutter_test: 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/GoogleMapController.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/GoogleMapController.swift index cc99b83f3ccd..6e023617ce5f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/GoogleMapController.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/GoogleMapController.swift @@ -59,7 +59,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV /// The Google Maps SDK map view managed by this controller. let mapView: GMSMapView /// The Pigeon callback API implementation, used to send events to the Dart side. - let dartCallbackHandler: MapsCallbackApi + let dartCallbackHandler: MapEventDelegate /// The main Pigeon API implementation, separate to avoid lifetime extension. let callHandler: MapCallHandler /// The inspector API implementation, separate to avoid lifetime extension. @@ -101,21 +101,52 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV options.mapID = GMSMapID(identifier: mapId) } + let binaryMessenger = registrar.messenger() + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) self.init( mapView: GMSMapView(options: options), viewIdentifier: viewId, creationParameters: creationParameters, assetProvider: DefaultAssetProvider(registrar: registrar), - binaryMessenger: registrar.messenger() + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler ) } - init( + convenience init( mapView: GMSMapView, viewIdentifier viewId: Int64, creationParameters: PlatformMapViewCreationParams, assetProvider: AssetProvider, binaryMessenger: FlutterBinaryMessenger + ) { + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) + self.init( + mapView: mapView, + viewIdentifier: viewId, + creationParameters: creationParameters, + assetProvider: assetProvider, + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler + ) + } + + init( + mapView: GMSMapView, + viewIdentifier viewId: Int64, + creationParameters: PlatformMapViewCreationParams, + assetProvider: AssetProvider, + binaryMessenger: FlutterBinaryMessenger, + callbackHandler: MapEventDelegate, + tileProvider: TileProviderDelegate ) { self.mapView = mapView mapView.accessibilityElementsHidden = false @@ -134,10 +165,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV // End duplicate code. let pigeonSuffix = String(format: "%lld", viewId) - dartCallbackHandler = MapsCallbackApi( - binaryMessenger: binaryMessenger, - messageChannelSuffix: pigeonSuffix - ) + dartCallbackHandler = callbackHandler let markerType = creationParameters.mapConfiguration.markerType @@ -167,7 +195,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV heatmapsController = HeatmapsController(mapView: mapView) tileOverlaysController = TileOverlaysController( mapView: mapView, - tileProvider: dartCallbackHandler + tileProvider: tileProvider ) groundOverlaysController = GroundOverlaysController( mapView: mapView, @@ -396,6 +424,15 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV } } + public func mapView( + _ mapView: GMSMapView, didTapPOIWithPlaceID placeID: String, name: String, + location: CLLocationCoordinate2D + ) { + Task { + try await dartCallbackHandler.didTapPointOfInterest(withPlaceIdentifier: placeID) + } + } + func interpretMapConfiguration(_ config: PlatformMapConfiguration) { // Any changes here must also be made to the `init` method above. See the comment there for // details. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/Messages.g.swift b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/Messages.g.swift index 3689467b7fea..0439edf54b0e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/Messages.g.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_sdk9/ios/google_maps_flutter_ios_sdk9/Sources/google_maps_flutter_ios_sdk9/Messages.g.swift @@ -3455,6 +3455,8 @@ protocol MapsCallbackApiProtocol { @MainActor func didTapInfoWindowOfMarker(withIdentifier markerIdArg: String) async throws /// Called when a circle is tapped. @MainActor func didTapCircle(withIdentifier circleIdArg: String) async throws + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws /// Called when a polygon is tapped. @@ -3738,6 +3740,29 @@ class MapsCallbackApi: MapsCallbackApiProtocol { } } } + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + return try await withCheckedThrowingContinuation { continuation in + let channelName: String = + "dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([placeIdArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + continuation.resume(throwing: createConnectionError(withChannelName: channelName)) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + continuation.resume(throwing: PigeonError(code: code, message: message, details: details)) + } else { + continuation.resume() + } + } + } + } /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws { return try await withCheckedThrowingContinuation { continuation in 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 11df18180a50..252912f722cd 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(); @@ -957,6 +962,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 d5c89d021360..76c32ea35680 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 @@ -3456,6 +3456,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); @@ -3751,6 +3754,31 @@ 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 { + 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_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 a9f2eae70806..a39bf066c24b 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 @@ -803,6 +803,10 @@ abstract class MapsCallbackApi { @SwiftFunction('didTapCircle(withIdentifier:)') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @SwiftFunction('didTapPointOfInterest(withPlaceIdentifier:)') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @SwiftFunction('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 0c3ef5d4faa8..ec94e485c48a 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.14 +version: 2.19.0 environment: sdk: ^3.11.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 meta: ^1.10.0 stream_transform: ^2.0.0 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 6d21a3e81c1f..978a022a4194 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 @@ -903,6 +903,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-id'; + + 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 e918732d5b1d..e63025ef02f7 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,27 @@ class StubBinaryMessenger: NSObject, FlutterBinaryMessenger { } } +/// Records POI tap callbacks for unit tests. +class MockMapEventHandler: TestMapEventHandler { + var lastTappedPointOfInterestPlaceIdentifier: String? + private var pointOfInterestTapContinuation: CheckedContinuation? + + func waitForPointOfInterestTap() async { + if lastTappedPointOfInterestPlaceIdentifier != nil { + return + } + await withCheckedContinuation { continuation in + pointOfInterestTapContinuation = continuation + } + } + + override func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + lastTappedPointOfInterestPlaceIdentifier = placeIdArg + pointOfInterestTapContinuation?.resume() + pointOfInterestTapContinuation = nil + } +} + class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { var viewController: UIViewController? { nil } func publish(_ value: NSObject) {} @@ -167,6 +188,37 @@ class StubPluginRegistrar: NSObject, FlutterPluginRegistrar { #expect(mockTransactionWrapper.animationDuration == Double(durationMilliseconds) / 1000) } + @Test func didTapPOIForwardsPlaceIdentifierToCallbackApi() async { + 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 mapEventHandler = MockMapEventHandler() + let controller = GoogleMapController( + mapView: mapView, + viewIdentifier: 0, + creationParameters: emptyCreationParameters(), + assetProvider: TestAssetProvider(), + binaryMessenger: StubBinaryMessenger(), + callbackHandler: mapEventHandler, + tileProvider: mapEventHandler + ) + + async let poiTapReceived: Void = mapEventHandler.waitForPointOfInterestTap() + controller.mapView( + mapView, + didTapPOIWithPlaceID: "place-123", + name: "Test Place", + location: CLLocationCoordinate2DMake(0, 0) + ) + await poiTapReceived + + #expect(mapEventHandler.lastTappedPointOfInterestPlaceIdentifier == "place-123") + } + @Test func inspectorAPICameraPosition() throws { let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let mapViewOptions = GMSMapViewOptions() 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 be9370c2f997..316b6b829bfd 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 @@ -6,8 +6,8 @@ import Foundation @testable import google_maps_flutter_ios -/// Fake implementation of MapEventDelegate for unit tests. -class TestMapEventHandler: MapEventDelegate { +/// Fake implementation of MapEventDelegate (and TileProviderDelegate) for unit tests. +class TestMapEventHandler: MapEventDelegate, TileProviderDelegate { func didStartCameraMove() async throws {} func didMoveCamera(to cameraPositionArg: PlatformCameraPosition) async throws {} @@ -39,6 +39,8 @@ class TestMapEventHandler: MapEventDelegate { func didTapCircle(withIdentifier circleIdArg: String) async throws {} + func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws {} + func didTapCluster(_ clusterArg: PlatformCluster) async throws {} func didTapPolygon(withIdentifier polygonIdArg: String) async throws {} 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/GoogleMapController.swift b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/GoogleMapController.swift index cc99b83f3ccd..6e023617ce5f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/GoogleMapController.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/GoogleMapController.swift @@ -59,7 +59,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV /// The Google Maps SDK map view managed by this controller. let mapView: GMSMapView /// The Pigeon callback API implementation, used to send events to the Dart side. - let dartCallbackHandler: MapsCallbackApi + let dartCallbackHandler: MapEventDelegate /// The main Pigeon API implementation, separate to avoid lifetime extension. let callHandler: MapCallHandler /// The inspector API implementation, separate to avoid lifetime extension. @@ -101,21 +101,52 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV options.mapID = GMSMapID(identifier: mapId) } + let binaryMessenger = registrar.messenger() + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) self.init( mapView: GMSMapView(options: options), viewIdentifier: viewId, creationParameters: creationParameters, assetProvider: DefaultAssetProvider(registrar: registrar), - binaryMessenger: registrar.messenger() + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler ) } - init( + convenience init( mapView: GMSMapView, viewIdentifier viewId: Int64, creationParameters: PlatformMapViewCreationParams, assetProvider: AssetProvider, binaryMessenger: FlutterBinaryMessenger + ) { + let callbackHandler = MapsCallbackApi( + binaryMessenger: binaryMessenger, + messageChannelSuffix: String(format: "%lld", viewId) + ) + self.init( + mapView: mapView, + viewIdentifier: viewId, + creationParameters: creationParameters, + assetProvider: assetProvider, + binaryMessenger: binaryMessenger, + callbackHandler: callbackHandler, + tileProvider: callbackHandler + ) + } + + init( + mapView: GMSMapView, + viewIdentifier viewId: Int64, + creationParameters: PlatformMapViewCreationParams, + assetProvider: AssetProvider, + binaryMessenger: FlutterBinaryMessenger, + callbackHandler: MapEventDelegate, + tileProvider: TileProviderDelegate ) { self.mapView = mapView mapView.accessibilityElementsHidden = false @@ -134,10 +165,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV // End duplicate code. let pigeonSuffix = String(format: "%lld", viewId) - dartCallbackHandler = MapsCallbackApi( - binaryMessenger: binaryMessenger, - messageChannelSuffix: pigeonSuffix - ) + dartCallbackHandler = callbackHandler let markerType = creationParameters.mapConfiguration.markerType @@ -167,7 +195,7 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV heatmapsController = HeatmapsController(mapView: mapView) tileOverlaysController = TileOverlaysController( mapView: mapView, - tileProvider: dartCallbackHandler + tileProvider: tileProvider ) groundOverlaysController = GroundOverlaysController( mapView: mapView, @@ -396,6 +424,15 @@ public class GoogleMapController: NSObject, GMSMapViewDelegate, FlutterPlatformV } } + public func mapView( + _ mapView: GMSMapView, didTapPOIWithPlaceID placeID: String, name: String, + location: CLLocationCoordinate2D + ) { + Task { + try await dartCallbackHandler.didTapPointOfInterest(withPlaceIdentifier: placeID) + } + } + func interpretMapConfiguration(_ config: PlatformMapConfiguration) { // Any changes here must also be made to the `init` method above. See the comment there for // details. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/Messages.g.swift b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/Messages.g.swift index 3689467b7fea..0439edf54b0e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/Messages.g.swift +++ b/packages/google_maps_flutter/google_maps_flutter_ios_shared_code/ios/google_maps_flutter_ios/Sources/google_maps_flutter_ios/Messages.g.swift @@ -3455,6 +3455,8 @@ protocol MapsCallbackApiProtocol { @MainActor func didTapInfoWindowOfMarker(withIdentifier markerIdArg: String) async throws /// Called when a circle is tapped. @MainActor func didTapCircle(withIdentifier circleIdArg: String) async throws + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws /// Called when a polygon is tapped. @@ -3738,6 +3740,29 @@ class MapsCallbackApi: MapsCallbackApiProtocol { } } } + /// Called when a point of interest is tapped. + @MainActor func didTapPointOfInterest(withPlaceIdentifier placeIdArg: String) async throws { + return try await withCheckedThrowingContinuation { continuation in + let channelName: String = + "dev.flutter.pigeon.google_maps_flutter_ios.MapsCallbackApi.onPointOfInterestTap\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel( + name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([placeIdArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + continuation.resume(throwing: createConnectionError(withChannelName: channelName)) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + continuation.resume(throwing: PigeonError(code: code, message: message, details: details)) + } else { + continuation.resume() + } + } + } + } /// Called when a marker cluster is tapped. @MainActor func didTapCluster(_ clusterArg: PlatformCluster) async throws { return try await withCheckedThrowingContinuation { continuation in 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 11df18180a50..252912f722cd 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(); @@ -957,6 +962,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 d5c89d021360..76c32ea35680 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 @@ -3456,6 +3456,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); @@ -3751,6 +3754,31 @@ 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 { + 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_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 5a87863f002a..81f43e8718e3 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 @@ -803,6 +803,10 @@ abstract class MapsCallbackApi { @SwiftFunction('didTapCircle(withIdentifier:)') void onCircleTap(String circleId); + /// Called when a point of interest is tapped. + @SwiftFunction('didTapPointOfInterest(withPlaceIdentifier:)') + void onPointOfInterestTap(String placeId); + /// Called when a marker cluster is tapped. @SwiftFunction('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 6d21a3e81c1f..978a022a4194 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 @@ -903,6 +903,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-id'; + + 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_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 46e832ee9907..e67dd293377b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.6.4 +* Adds support for tapping points of interest on the map. * Updates minimum supported SDK version to Flutter 3.41/Dart 3.11. ## 0.6.3+1 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 2a13f0e8c840..f62a36414ec0 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 @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.16.0 + google_maps_flutter_platform_interface: ^2.17.0 google_maps_flutter_web: path: ../.. web: ^1.0.0 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 1d8062ca9833..9d95b069f242 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 @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - google_maps_flutter_platform_interface: ^2.16.0 + google_maps_flutter_platform_interface: ^2.17.0 google_maps_flutter_web: path: ../.. web: ^1.0.0 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..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 @@ -24,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 13e9054d9c7c..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,6 +289,11 @@ class GoogleMapController { _onClickSubscription = map.onClick.listen((gmaps.MapMouseEventOrIconMouseEvent event) { assert(event.latLng != null); if (!_streamController.isClosed) { + 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/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/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..66584d0f1743 --- /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? get placeId; +} 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 925a553fe61a..e5688ef1ec2e 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+1 +version: 0.6.4 environment: sdk: ^3.11.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