diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 815987add6c..50425852d88 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.12.0 + +* Adds support for animating the camera with a duration. + ## 2.11.0 * Adds support for ground overlays. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart index dc6973a5e8f..6b0ad280b05 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart @@ -13,6 +13,20 @@ import 'package:integration_test/integration_test.dart'; import 'shared.dart'; +const double _kTestCameraZoomLevel = 10; +const double _kTestZoomByAmount = 2; +const LatLng _kTestMapCenter = LatLng(65, 25.5); +const CameraPosition _kTestCameraPosition = CameraPosition( + target: _kTestMapCenter, + zoom: _kTestCameraZoomLevel, + bearing: 1.0, + tilt: 1.0, +); +final LatLngBounds _testCameraBounds = LatLngBounds( + northeast: const LatLng(50, -65), southwest: const LatLng(28.5, -123)); +final ValueVariant _cameraUpdateTypeVariants = + ValueVariant(CameraUpdateType.values.toSet()); + /// Integration Tests that use the [GoogleMapsInspectorPlatform]. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -612,6 +626,248 @@ void runTests() { expect(clusters.length, 0); } }); + + testWidgets( + 'testAnimateCameraWithoutDuration', + (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + final GoogleMapsInspectorPlatform inspector = + GoogleMapsInspectorPlatform.instance!; + + /// Completer to track when the camera has come to rest. + Completer? cameraIdleCompleter; + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: kInitialCameraPosition, + onCameraIdle: () { + if (cameraIdleCompleter != null && + !cameraIdleCompleter.isCompleted) { + cameraIdleCompleter.complete(); + } + }, + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final GoogleMapController controller = await controllerCompleter.future; + + await tester.pumpAndSettle(); + // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and + // `mapControllerCompleter.complete(controller)` above should happen in + // `mapRendered`. + // https://github.com/flutter/flutter/issues/54758 + await Future.delayed(const Duration(seconds: 1)); + + // Create completer for camera idle event. + cameraIdleCompleter = Completer(); + + final CameraUpdate cameraUpdate = + _getCameraUpdateForType(_cameraUpdateTypeVariants.currentValue!); + await controller.animateCamera(cameraUpdate); + + // If platform supportes getting camera position, check that the camera + // has moved as expected. + CameraPosition? beforeFinishedPosition; + if (inspector.supportsGettingGameraPosition()) { + // Immediately after calling animateCamera, check that the camera hasn't + // reached its final position. This relies on the assumption that the + // camera move is animated and won't complete instantly. + beforeFinishedPosition = + await inspector.getCameraPosition(mapId: controller.mapId); + + await _checkCameraUpdateByType( + _cameraUpdateTypeVariants.currentValue!, + beforeFinishedPosition, + null, + controller, + (Matcher matcher) => isNot(matcher)); + } + + // Wait for the animation to complete (onCameraIdle). + expect(cameraIdleCompleter.isCompleted, isFalse); + await cameraIdleCompleter.future; + + // If platform supportes getting camera position, check that the camera + // has moved as expected. + if (inspector.supportsGettingGameraPosition()) { + // After onCameraIdle event, the camera should be at the final position. + final CameraPosition afterFinishedPosition = + await inspector.getCameraPosition(mapId: controller.mapId); + await _checkCameraUpdateByType( + _cameraUpdateTypeVariants.currentValue!, + afterFinishedPosition, + beforeFinishedPosition, + controller, + (Matcher matcher) => matcher); + } + }, + variant: _cameraUpdateTypeVariants, + // TODO(stuartmorgan): Remove skip for Android platform once Maps API key is + // available for LUCI, https://github.com/flutter/flutter/issues/131071 + skip: isAndroid, + ); + + /// Tests animating the camera with specified durations to verify timing + /// behavior. + /// + /// This test checks two scenarios: short and long animation durations. + /// It uses a midpoint duration to ensure the short animation completes in + /// less time and the long animation takes more time than that midpoint. + /// This ensures that the animation duration is respected by the platform and + /// that the default camera animation duration does not affect the test + /// results. + testWidgets( + 'testAnimateCameraWithDuration', + (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + final GoogleMapsInspectorPlatform inspector = + GoogleMapsInspectorPlatform.instance!; + + /// Completer to track when the camera has come to rest. + Completer? cameraIdleCompleter; + + const int shortCameraAnimationDurationMS = 200; + const int longCameraAnimationDurationMS = 1000; + + /// Calculate the midpoint duration of the animation test, which will + /// serve as a reference to verify that animations complete more quickly + /// with shorter durations and more slowly with longer durations. + const int animationDurationMiddlePoint = + (shortCameraAnimationDurationMS + longCameraAnimationDurationMS) ~/ 2; + + // Stopwatch to measure the time taken for the animation to complete. + final Stopwatch stopwatch = Stopwatch(); + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: kInitialCameraPosition, + onCameraIdle: () { + if (cameraIdleCompleter != null && + !cameraIdleCompleter.isCompleted) { + stopwatch.stop(); + cameraIdleCompleter.complete(); + } + }, + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + + final GoogleMapController controller = await controllerCompleter.future; + + await tester.pumpAndSettle(); + // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and + // `mapControllerCompleter.complete(controller)` above should happen in + // `mapRendered`. + // https://github.com/flutter/flutter/issues/54758 + await Future.delayed(const Duration(seconds: 1)); + + // Create completer for camera idle event. + cameraIdleCompleter = Completer(); + + // Start stopwatch to check the time taken for the animation to complete. + // Stopwatch is stopped on camera idle callback. + stopwatch.reset(); + stopwatch.start(); + + // First phase with shorter animation duration. + final CameraUpdate cameraUpdateShort = + _getCameraUpdateForType(_cameraUpdateTypeVariants.currentValue!); + await controller.animateCamera( + cameraUpdateShort, + duration: const Duration(milliseconds: shortCameraAnimationDurationMS), + ); + + // Wait for the animation to complete (onCameraIdle). + expect(cameraIdleCompleter.isCompleted, isFalse); + await cameraIdleCompleter.future; + + // For short animation duration, check that the animation is completed + // faster than the midpoint benchmark. + expect(stopwatch.elapsedMilliseconds, + lessThan(animationDurationMiddlePoint)); + + // Reset camera to initial position before testing long duration. + await controller + .moveCamera(CameraUpdate.newCameraPosition(kInitialCameraPosition)); + await tester.pumpAndSettle(); + + // Create completer for camera idle event. + cameraIdleCompleter = Completer(); + + // Start stopwatch to check the time taken for the animation to complete. + // Stopwatch is stopped on camera idle callback. + stopwatch.reset(); + stopwatch.start(); + + // Second phase with longer animation duration. + final CameraUpdate cameraUpdateLong = + _getCameraUpdateForType(_cameraUpdateTypeVariants.currentValue!); + await controller.animateCamera( + cameraUpdateLong, + duration: const Duration(milliseconds: longCameraAnimationDurationMS), + ); + + // If platform supportes getting camera position, check that the camera + // has moved as expected. + CameraPosition? beforeFinishedPosition; + if (inspector.supportsGettingGameraPosition()) { + // Immediately after calling animateCamera, check that the camera hasn't + // reached its final position. This relies on the assumption that the + // camera move is animated and won't complete instantly. + beforeFinishedPosition = + await inspector.getCameraPosition(mapId: controller.mapId); + + await _checkCameraUpdateByType( + _cameraUpdateTypeVariants.currentValue!, + beforeFinishedPosition, + null, + controller, + (Matcher matcher) => isNot(matcher)); + } + + // Wait for the animation to complete (onCameraIdle). + expect(cameraIdleCompleter.isCompleted, isFalse); + await cameraIdleCompleter.future; + + // For longer animation duration, check that the animation is completed + // slower than the midpoint benchmark. + expect(stopwatch.elapsedMilliseconds, + greaterThan(animationDurationMiddlePoint)); + + // If platform supportes getting camera position, check that the camera + // has moved as expected. + if (inspector.supportsGettingGameraPosition()) { + // Camera should be at the final position. + final CameraPosition afterFinishedPosition = + await inspector.getCameraPosition(mapId: controller.mapId); + await _checkCameraUpdateByType( + _cameraUpdateTypeVariants.currentValue!, + afterFinishedPosition, + beforeFinishedPosition, + controller, + (Matcher matcher) => matcher); + } + }, + variant: _cameraUpdateTypeVariants, + // TODO(jokerttu): Remove skip once the web implementation is available, + // https://github.com/flutter/flutter/issues/159265 + // TODO(stuartmorgan): Remove skip for Android platform once Maps API key is + // available for LUCI, https://github.com/flutter/flutter/issues/131071 + skip: kIsWeb || isAndroid, + ); } Marker _copyMarkerWithClusterManagerId( @@ -636,3 +892,89 @@ Marker _copyMarkerWithClusterManagerId( clusterManagerId: clusterManagerId, ); } + +CameraUpdate _getCameraUpdateForType(CameraUpdateType type) { + return switch (type) { + CameraUpdateType.newCameraPosition => + CameraUpdate.newCameraPosition(_kTestCameraPosition), + CameraUpdateType.newLatLng => CameraUpdate.newLatLng(_kTestMapCenter), + CameraUpdateType.newLatLngBounds => + CameraUpdate.newLatLngBounds(_testCameraBounds, 0), + CameraUpdateType.newLatLngZoom => + CameraUpdate.newLatLngZoom(_kTestMapCenter, _kTestCameraZoomLevel), + CameraUpdateType.scrollBy => CameraUpdate.scrollBy(10, 10), + CameraUpdateType.zoomBy => + CameraUpdate.zoomBy(_kTestZoomByAmount, const Offset(1, 1)), + CameraUpdateType.zoomTo => CameraUpdate.zoomTo(_kTestCameraZoomLevel), + CameraUpdateType.zoomIn => CameraUpdate.zoomIn(), + CameraUpdateType.zoomOut => CameraUpdate.zoomOut(), + }; +} + +Future _checkCameraUpdateByType( + CameraUpdateType type, + CameraPosition currentPosition, + CameraPosition? oldPosition, + GoogleMapController controller, + Matcher Function(Matcher matcher) wrapMatcher, +) async { + // As the target might differ a bit from the expected target, a threshold is + // used. + const double latLngThreshold = 0.05; + + switch (type) { + case CameraUpdateType.newCameraPosition: + expect(currentPosition.bearing, + wrapMatcher(equals(_kTestCameraPosition.bearing))); + expect( + currentPosition.zoom, wrapMatcher(equals(_kTestCameraPosition.zoom))); + expect( + currentPosition.tilt, wrapMatcher(equals(_kTestCameraPosition.tilt))); + expect( + currentPosition.target.latitude, + wrapMatcher( + closeTo(_kTestCameraPosition.target.latitude, latLngThreshold))); + expect( + currentPosition.target.longitude, + wrapMatcher( + closeTo(_kTestCameraPosition.target.longitude, latLngThreshold))); + case CameraUpdateType.newLatLng: + expect(currentPosition.target.latitude, + wrapMatcher(closeTo(_kTestMapCenter.latitude, latLngThreshold))); + expect(currentPosition.target.longitude, + wrapMatcher(closeTo(_kTestMapCenter.longitude, latLngThreshold))); + case CameraUpdateType.newLatLngBounds: + final LatLngBounds bounds = await controller.getVisibleRegion(); + expect( + bounds.northeast.longitude, + wrapMatcher( + closeTo(_testCameraBounds.northeast.longitude, latLngThreshold))); + expect( + bounds.southwest.longitude, + wrapMatcher( + closeTo(_testCameraBounds.southwest.longitude, latLngThreshold))); + case CameraUpdateType.newLatLngZoom: + expect(currentPosition.target.latitude, + wrapMatcher(closeTo(_kTestMapCenter.latitude, latLngThreshold))); + expect(currentPosition.target.longitude, + wrapMatcher(closeTo(_kTestMapCenter.longitude, latLngThreshold))); + expect(currentPosition.zoom, wrapMatcher(equals(_kTestCameraZoomLevel))); + case CameraUpdateType.scrollBy: + // For scrollBy, just check that the location has changed. + if (oldPosition != null) { + expect(currentPosition.target.latitude, + isNot(equals(oldPosition.target.latitude))); + expect(currentPosition.target.longitude, + isNot(equals(oldPosition.target.longitude))); + } + case CameraUpdateType.zoomBy: + expect(currentPosition.zoom, + wrapMatcher(equals(kInitialZoomLevel + _kTestZoomByAmount))); + case CameraUpdateType.zoomTo: + expect(currentPosition.zoom, wrapMatcher(equals(_kTestCameraZoomLevel))); + case CameraUpdateType.zoomIn: + expect(currentPosition.zoom, wrapMatcher(equals(kInitialZoomLevel + 1))); + case CameraUpdateType.zoomOut: + expect(currentPosition.zoom, wrapMatcher(equals(kInitialZoomLevel - 1))); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/animate_camera.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/animate_camera.dart index 593c533125c..bd67f1b713c 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/animate_camera.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/animate_camera.dart @@ -4,6 +4,7 @@ // ignore_for_file: public_member_api_docs +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -25,14 +26,26 @@ class AnimateCamera extends StatefulWidget { State createState() => AnimateCameraState(); } +// Duration for camera animation in seconds. +const int _durationSeconds = 10; + class AnimateCameraState extends State { GoogleMapController? mapController; + Duration? _cameraUpdateAnimationDuration; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { mapController = controller; } + void _toggleAnimationDuration() { + setState(() { + _cameraUpdateAnimationDuration = _cameraUpdateAnimationDuration != null + ? null + : const Duration(seconds: _durationSeconds); + }); + } + @override Widget build(BuildContext context) { return Column( @@ -66,6 +79,7 @@ class AnimateCameraState extends State { zoom: 17.0, ), ), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('newCameraPosition'), @@ -76,6 +90,7 @@ class AnimateCameraState extends State { CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('newLatLng'), @@ -90,6 +105,7 @@ class AnimateCameraState extends State { ), 10.0, ), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('newLatLngBounds'), @@ -101,6 +117,7 @@ class AnimateCameraState extends State { const LatLng(37.4231613, -122.087159), 11.0, ), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('newLatLngZoom'), @@ -109,6 +126,7 @@ class AnimateCameraState extends State { onPressed: () { mapController?.animateCamera( CameraUpdate.scrollBy(150.0, -225.0), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('scrollBy'), @@ -124,6 +142,7 @@ class AnimateCameraState extends State { -0.5, const Offset(30.0, 20.0), ), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('zoomBy with focus'), @@ -132,6 +151,7 @@ class AnimateCameraState extends State { onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy(-0.5), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('zoomBy'), @@ -140,6 +160,7 @@ class AnimateCameraState extends State { onPressed: () { mapController?.animateCamera( CameraUpdate.zoomIn(), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('zoomIn'), @@ -148,6 +169,7 @@ class AnimateCameraState extends State { onPressed: () { mapController?.animateCamera( CameraUpdate.zoomOut(), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('zoomOut'), @@ -156,6 +178,7 @@ class AnimateCameraState extends State { onPressed: () { mapController?.animateCamera( CameraUpdate.zoomTo(16.0), + duration: _cameraUpdateAnimationDuration, ); }, child: const Text('zoomTo'), @@ -163,7 +186,25 @@ class AnimateCameraState extends State { ], ), ], - ) + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'With 10 second duration', + textAlign: TextAlign.right, + ), + const SizedBox(width: 5), + Switch( + value: _cameraUpdateAnimationDuration != null, + onChanged: kIsWeb + ? null + : (bool value) { + _toggleAnimationDuration(); + }, + ), + ], + ), ], ); } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index dcc3af07cfb..b686cbf766c 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -18,8 +18,8 @@ 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_android: ^2.15.0 - google_maps_flutter_platform_interface: ^2.10.0 + google_maps_flutter_android: ^2.16.0 + google_maps_flutter_platform_interface: ^2.11.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 58905898998..46fc95a70d2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -202,11 +202,15 @@ class GoogleMapController { /// Starts an animated change of the map camera position. /// + /// The [duration] parameter specifies the duration of the animation. + /// If null, the platform will decide the default value. + /// /// The returned [Future] completes after the change has been started on the /// platform side. - Future animateCamera(CameraUpdate cameraUpdate) { - return GoogleMapsFlutterPlatform.instance - .animateCamera(cameraUpdate, mapId: mapId); + Future animateCamera(CameraUpdate cameraUpdate, {Duration? duration}) { + return GoogleMapsFlutterPlatform.instance.animateCameraWithConfiguration( + cameraUpdate, CameraUpdateAnimationConfiguration(duration: duration), + mapId: mapId); } /// Changes the map camera position. diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index dd9c3f3a4be..64ac924ec1e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.11.0 +version: 2.12.0 environment: sdk: ^3.6.0 @@ -21,9 +21,9 @@ flutter: dependencies: flutter: sdk: flutter - google_maps_flutter_android: ^2.15.0 - google_maps_flutter_ios: ^2.14.0 - google_maps_flutter_platform_interface: ^2.10.0 + google_maps_flutter_android: ^2.16.0 + google_maps_flutter_ios: ^2.15.0 + google_maps_flutter_platform_interface: ^2.11.0 google_maps_flutter_web: ^0.5.12 dev_dependencies: diff --git a/packages/google_maps_flutter/google_maps_flutter/test/camera_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/camera_test.dart new file mode 100644 index 00000000000..d4725709236 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/test/camera_test.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +import 'fake_google_maps_flutter_platform.dart'; + +void main() { + late FakeGoogleMapsFlutterPlatform platform; + + setUp(() { + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; + }); + + testWidgets('Can animate camera with duration', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + )); + final GoogleMapController controller = await controllerCompleter.future; + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.animateCameraConfiguration, isNull); + + final CameraUpdate newCameraUpdate = + CameraUpdate.newLatLng(const LatLng(20.0, 25.0)); + const Duration updateDuration = Duration(seconds: 10); + + await controller.animateCamera( + newCameraUpdate, + duration: updateDuration, + ); + + expect(map.animateCameraConfiguration, isNotNull); + expect(map.animateCameraConfiguration!.cameraUpdate, newCameraUpdate); + expect(map.animateCameraConfiguration!.configuration?.duration, + updateDuration); + + /// Tests that the camera update respects the default behavior when the + /// duration is null. + await controller.animateCamera( + newCameraUpdate, + ); + expect(map.animateCameraConfiguration!.configuration?.duration, isNull); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart index 07c99b53807..3910fb5f9f3 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart @@ -131,7 +131,28 @@ class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, - }) async {} + }) async { + mapInstances[mapId]?.animateCameraConfiguration = + CameraUpdateWithConfiguration( + cameraUpdate: cameraUpdate, + configuration: null, + ); + await _fakeDelay(); + } + + @override + Future animateCameraWithConfiguration( + CameraUpdate cameraUpdate, + CameraUpdateAnimationConfiguration configuration, { + required int mapId, + }) async { + mapInstances[mapId]?.animateCameraConfiguration = + CameraUpdateWithConfiguration( + cameraUpdate: cameraUpdate, + configuration: configuration, + ); + await _fakeDelay(); + } @override Future moveCamera( @@ -337,6 +358,7 @@ class PlatformMapStateRecorder { MapWidgetConfiguration widgetConfiguration; MapObjects mapObjects; MapConfiguration mapConfiguration; + CameraUpdateWithConfiguration? animateCameraConfiguration; final List markerUpdates = []; final List polygonUpdates = []; @@ -349,3 +371,14 @@ class PlatformMapStateRecorder { final List groundOverlayUpdates = []; } + +/// Helper class to store animateCameraWithConfiguration data. +class CameraUpdateWithConfiguration { + CameraUpdateWithConfiguration({ + required this.cameraUpdate, + required this.configuration, + }); + + final CameraUpdate cameraUpdate; + final CameraUpdateAnimationConfiguration? configuration; +}