From 0ce2216963bfe284393849a89b70ed2f15102e46 Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Fri, 19 Jun 2020 14:00:18 +1200 Subject: [PATCH] Add drag event to platform interface --- .../CHANGELOG.md | 4 + .../lib/src/events/map_event.dart | 20 +++ .../method_channel_google_maps_flutter.dart | 24 +++ .../google_maps_flutter_platform.dart | 10 ++ .../lib/src/types/marker.dart | 15 +- .../pubspec.yaml | 3 +- ...thod_channel_google_maps_flutter_test.dart | 54 ++++++ .../test/types/marker_test.dart | 167 ++++++++++++++++++ 8 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 5d361d8e0c7c..464c33ed754e 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.1.2 + +* Add additional marker drag events + ## 2.1.1 * Method `buildViewWithTextDirection` has been added to the platform interface. 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 be426483193d..614cbe8e29fb 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 @@ -102,6 +102,26 @@ class InfoWindowTapEvent extends MapEvent { InfoWindowTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId); } +/// An event fired when a [Marker] is starting to be dragged to a new [LatLng]. +class MarkerDragStartEvent extends _PositionedMapEvent { + /// Build a MarkerDragStart Event triggered from the map represented by `mapId`. + /// + /// The `position` on this event is the [LatLng] on which the Marker was picked up from. + /// The `value` of this event is a [MarkerId] object that represents the Marker. + MarkerDragStartEvent(int mapId, LatLng position, MarkerId markerId) + : super(mapId, position, markerId); +} + +/// An event fired when a [Marker] is being dragged to a new [LatLng]. +class MarkerDragEvent extends _PositionedMapEvent { + /// Build a MarkerDrag Event triggered from the map represented by `mapId`. + /// + /// The `position` on this event is the [LatLng] on which the Marker was dragged to. + /// The `value` of this event is a [MarkerId] object that represents the Marker. + MarkerDragEvent(int mapId, LatLng position, MarkerId markerId) + : super(mapId, position, markerId); +} + /// An event fired when a [Marker] is dragged to a new [LatLng]. class MarkerDragEndEvent extends _PositionedMapEvent { /// Build a MarkerDragEnd Event triggered from the map represented by `mapId`. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart index 2b9c71ee85bd..99f4fddaccd3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -124,6 +124,16 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return _events(mapId).whereType(); } + @override + Stream onMarkerDragStart({required int mapId}) { + return _events(mapId).whereType(); + } + + @override + Stream onMarkerDrag({required int mapId}) { + return _events(mapId).whereType(); + } + @override Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); @@ -174,6 +184,20 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { MarkerId(call.arguments['markerId']), )); break; + case 'marker#onDragStart': + _mapEventStreamController.add(MarkerDragStartEvent( + mapId, + LatLng.fromJson(call.arguments['position'])!, + MarkerId(call.arguments['markerId']), + )); + break; + case 'marker#onDrag': + _mapEventStreamController.add(MarkerDragEvent( + mapId, + LatLng.fromJson(call.arguments['position'])!, + MarkerId(call.arguments['markerId']), + )); + break; case 'marker#onDragEnd': _mapEventStreamController.add(MarkerDragEndEvent( 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 2bb0ab2588f9..08b4872ad5dd 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 @@ -303,6 +303,16 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('onInfoWindowTap() has not been implemented.'); } + /// A [Marker] has been dragged to a different [LatLng] position. + Stream onMarkerDragStart({required int mapId}) { + throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); + } + + /// A [Marker] has been dragged to a different [LatLng] position. + Stream onMarkerDrag({required int mapId}) { + throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); + } + /// A [Marker] has been dragged to a different [LatLng] position. Stream onMarkerDragEnd({required int mapId}) { throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart index 0d1b780c24d2..52255f84f4cc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart @@ -147,6 +147,8 @@ class Marker implements MapsObject { this.visible = true, this.zIndex = 0.0, this.onTap, + this.onDrag, + this.onDragStart, this.onDragEnd, }) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0)); @@ -207,9 +209,15 @@ class Marker implements MapsObject { /// Callbacks to receive tap events for markers placed on this map. final VoidCallback? onTap; + /// Signature reporting the new [LatLng] at the start of a drag event. + final ValueChanged? onDragStart; + /// Signature reporting the new [LatLng] at the end of a drag event. final ValueChanged? onDragEnd; + /// Signature reporting the new [LatLng] during the drag event. + final ValueChanged? onDrag; + /// Creates a new [Marker] object whose values are the same as this instance, /// unless overwritten by the specified parameters. Marker copyWith({ @@ -225,6 +233,8 @@ class Marker implements MapsObject { bool? visibleParam, double? zIndexParam, VoidCallback? onTapParam, + ValueChanged? onDragStartParam, + ValueChanged? onDragParam, ValueChanged? onDragEndParam, }) { return Marker( @@ -241,6 +251,8 @@ class Marker implements MapsObject { visible: visibleParam ?? visible, zIndex: zIndexParam ?? zIndex, onTap: onTapParam ?? onTap, + onDragStart: onDragStartParam ?? onDragStart, + onDrag: onDragParam ?? onDrag, onDragEnd: onDragEndParam ?? onDragEnd, ); } @@ -300,6 +312,7 @@ class Marker implements MapsObject { return 'Marker{markerId: $markerId, alpha: $alpha, anchor: $anchor, ' 'consumeTapEvents: $consumeTapEvents, draggable: $draggable, flat: $flat, ' 'icon: $icon, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' - 'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; + 'visible: $visible, zIndex: $zIndex, onTap: $onTap, onDragStart: $onDragStart, ' + 'onDrag: $onDrag, onDragEnd: $onDragEnd}'; } } 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 1dc73f442d2e..2a2c9cfa8b46 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/plugins/tree/master/packages/google_maps_ 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.1.1 +version: 2.1.2 environment: sdk: '>=2.12.0 <3.0.0' @@ -19,6 +19,7 @@ dependencies: stream_transform: ^2.0.0 dev_dependencies: + async: ^2.5.0 flutter_test: sdk: flutter mockito: ^5.0.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart index 19e81c960839..176f702ff0ff 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart @@ -5,8 +5,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/src/events/map_event.dart'; import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'dart:async'; + +import 'package:async/async.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -33,6 +37,15 @@ void main() { }); } + Future sendPlatformMessage( + int mapId, String method, Map data) async { + final ByteData byteData = const StandardMethodCodec() + .encodeMethodCall(MethodCall(method, data)); + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + .handlePlatformMessage( + "plugins.flutter.io/google_maps_$mapId", byteData, (data) {}); + } + // Calls each method that uses invokeMethod with a return type other than // void to ensure that the casting/nullability handling succeeds. // @@ -68,5 +81,46 @@ void main() { 'map#takeSnapshot', ]); }); + test('markers send drag event to correct streams', () async { + const int mapId = 1; + final jsonMarkerDragStartEvent = { + "mapId": mapId, + "markerId": "drag-start-marker", + "position": [1.0, 1.0] + }; + final jsonMarkerDragEvent = { + "mapId": mapId, + "markerId": "drag-marker", + "position": [1.0, 1.0] + }; + final jsonMarkerDragEndEvent = { + "mapId": mapId, + "markerId": "drag-end-marker", + "position": [1.0, 1.0] + }; + + final MethodChannelGoogleMapsFlutter maps = + MethodChannelGoogleMapsFlutter(); + maps.ensureChannelInitialized(mapId); + + final StreamQueue markerDragStartStream = + StreamQueue(maps.onMarkerDragStart(mapId: mapId)); + final StreamQueue markerDragStream = + StreamQueue(maps.onMarkerDrag(mapId: mapId)); + final StreamQueue markerDragEndStream = + StreamQueue(maps.onMarkerDragEnd(mapId: mapId)); + + await sendPlatformMessage( + mapId, "marker#onDragStart", jsonMarkerDragStartEvent); + await sendPlatformMessage(mapId, "marker#onDrag", jsonMarkerDragEvent); + await sendPlatformMessage( + mapId, "marker#onDragEnd", jsonMarkerDragEndEvent); + + expect((await markerDragStartStream.next).value.value, + equals("drag-start-marker")); + expect((await markerDragStream.next).value.value, equals("drag-marker")); + expect((await markerDragEndStream.next).value.value, + equals("drag-end-marker")); + }); }); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart new file mode 100644 index 000000000000..c8f6fa527a95 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart @@ -0,0 +1,167 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$Marker', () { + test('constructor defaults', () { + final Marker marker = Marker(markerId: MarkerId("ABC123")); + + expect(marker.alpha, equals(1.0)); + expect(marker.anchor, equals(const Offset(0.5, 1.0))); + expect(marker.consumeTapEvents, equals(false)); + expect(marker.draggable, equals(false)); + expect(marker.flat, equals(false)); + expect(marker.icon, equals(BitmapDescriptor.defaultMarker)); + expect(marker.infoWindow, equals(InfoWindow.noText)); + expect(marker.position, equals(const LatLng(0.0, 0.0))); + expect(marker.rotation, equals(0.0)); + expect(marker.visible, equals(true)); + expect(marker.zIndex, equals(0.0)); + expect(marker.onTap, equals(null)); + expect(marker.onDrag, equals(null)); + expect(marker.onDragStart, equals(null)); + expect(marker.onDragEnd, equals(null)); + }); + test('constructor alpha is >= 0.0 and <= 1.0', () { + final ValueSetter initWithAlpha = (double alpha) { + Marker(markerId: MarkerId("ABC123"), alpha: alpha); + }; + expect(() => initWithAlpha(-0.5), throwsAssertionError); + expect(() => initWithAlpha(0.0), isNot(throwsAssertionError)); + expect(() => initWithAlpha(0.5), isNot(throwsAssertionError)); + expect(() => initWithAlpha(1.0), isNot(throwsAssertionError)); + expect(() => initWithAlpha(100), throwsAssertionError); + }); + + test('toJson', () { + final BitmapDescriptor testDescriptor = + BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); + final Marker marker = Marker( + markerId: MarkerId("ABC123"), + alpha: 0.12345, + anchor: Offset(100, 100), + consumeTapEvents: true, + draggable: true, + flat: true, + icon: testDescriptor, + infoWindow: InfoWindow( + title: "Test title", + snippet: "Test snippet", + anchor: Offset(100, 200), + ), + position: LatLng(50, 50), + rotation: 100, + visible: false, + zIndex: 100, + onTap: () {}, + onDragStart: (LatLng latLng) {}, + onDrag: (LatLng latLng) {}, + onDragEnd: (LatLng latLng) {}, + ); + + final Map json = marker.toJson() as Map; + + expect(json, { + 'markerId': "ABC123", + 'alpha': 0.12345, + 'anchor': [100, 100], + 'consumeTapEvents': true, + 'draggable': true, + 'flat': true, + 'icon': testDescriptor.toJson(), + 'infoWindow': { + 'title': "Test title", + 'snippet': "Test snippet", + 'anchor': [100.0, 200.0], + }, + 'position': [50, 50], + 'rotation': 100.0, + 'visible': false, + 'zIndex': 100.0, + }); + }); + test('clone', () { + final Marker marker = Marker(markerId: MarkerId("ABC123")); + final Marker clone = marker.clone(); + + expect(identical(clone, marker), isFalse); + expect(clone, equals(marker)); + }); + test('copyWith', () { + final Marker marker = Marker(markerId: MarkerId("ABC123")); + + final BitmapDescriptor testDescriptor = + BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); + final double testAlphaParam = 0.12345; + final Offset testAnchorParam = Offset(100, 100); + final bool testConsumeTapEventsParam = !marker.consumeTapEvents; + final bool testDraggableParam = !marker.draggable; + final bool testFlatParam = !marker.flat; + final BitmapDescriptor testIconParam = testDescriptor; + final InfoWindow testInfoWindowParam = InfoWindow(title: "Test"); + final LatLng testPositionParam = LatLng(100, 100); + final double testRotationParam = 100; + final bool testVisibleParam = !marker.visible; + final double testZIndexParam = 100; + final List log = []; + + final copy = marker.copyWith( + alphaParam: testAlphaParam, + anchorParam: testAnchorParam, + consumeTapEventsParam: testConsumeTapEventsParam, + draggableParam: testDraggableParam, + flatParam: testFlatParam, + iconParam: testIconParam, + infoWindowParam: testInfoWindowParam, + positionParam: testPositionParam, + rotationParam: testRotationParam, + visibleParam: testVisibleParam, + zIndexParam: testZIndexParam, + onTapParam: () { + log.add("onTapParam"); + }, + onDragStartParam: (LatLng latLng) { + log.add("onDragStartParam"); + }, + onDragParam: (LatLng latLng) { + log.add("onDragParam"); + }, + onDragEndParam: (LatLng latLng) { + log.add("onDragEndParam"); + }, + ); + + expect(copy.alpha, equals(testAlphaParam)); + expect(copy.anchor, equals(testAnchorParam)); + expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam)); + expect(copy.draggable, equals(testDraggableParam)); + expect(copy.flat, equals(testFlatParam)); + expect(copy.icon, equals(testIconParam)); + expect(copy.infoWindow, equals(testInfoWindowParam)); + expect(copy.position, equals(testPositionParam)); + expect(copy.rotation, equals(testRotationParam)); + expect(copy.visible, equals(testVisibleParam)); + expect(copy.zIndex, equals(testZIndexParam)); + + copy.onTap!(); + expect(log, contains("onTapParam")); + + copy.onDragStart!(LatLng(0, 1)); + expect(log, contains("onDragStartParam")); + + copy.onDrag!(LatLng(0, 1)); + expect(log, contains("onDragParam")); + + copy.onDragEnd!(LatLng(0, 1)); + expect(log, contains("onDragEndParam")); + }); + }); +}