blob: c28ff1f4f55f30d6cd04af35649b17c55a678585 [file] [log] [blame]
// 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:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class FakePlatformGoogleMap {
FakePlatformGoogleMap(int id, Map<dynamic, dynamic> params)
: cameraPosition =
CameraPosition.fromMap(params['initialCameraPosition']),
channel = MethodChannel('plugins.flutter.io/google_maps_$id') {
_ambiguate(TestDefaultBinaryMessengerBinding.instance)!
.defaultBinaryMessenger
.setMockMethodCallHandler(channel, onMethodCall);
updateOptions(params['options'] as Map<dynamic, dynamic>);
updateMarkers(params);
updatePolygons(params);
updatePolylines(params);
updateCircles(params);
updateTileOverlays(Map.castFrom<dynamic, dynamic, String, dynamic>(params));
}
MethodChannel channel;
CameraPosition? cameraPosition;
bool? compassEnabled;
bool? mapToolbarEnabled;
CameraTargetBounds? cameraTargetBounds;
MapType? mapType;
MinMaxZoomPreference? minMaxZoomPreference;
bool? rotateGesturesEnabled;
bool? scrollGesturesEnabled;
bool? tiltGesturesEnabled;
bool? zoomGesturesEnabled;
bool? zoomControlsEnabled;
bool? liteModeEnabled;
bool? trackCameraPosition;
bool? myLocationEnabled;
bool? trafficEnabled;
bool? buildingsEnabled;
bool? myLocationButtonEnabled;
List<dynamic>? padding;
Set<MarkerId> markerIdsToRemove = <MarkerId>{};
Set<Marker> markersToAdd = <Marker>{};
Set<Marker> markersToChange = <Marker>{};
Set<PolygonId> polygonIdsToRemove = <PolygonId>{};
Set<Polygon> polygonsToAdd = <Polygon>{};
Set<Polygon> polygonsToChange = <Polygon>{};
Set<PolylineId> polylineIdsToRemove = <PolylineId>{};
Set<Polyline> polylinesToAdd = <Polyline>{};
Set<Polyline> polylinesToChange = <Polyline>{};
Set<CircleId> circleIdsToRemove = <CircleId>{};
Set<Circle> circlesToAdd = <Circle>{};
Set<Circle> circlesToChange = <Circle>{};
Set<TileOverlayId> tileOverlayIdsToRemove = <TileOverlayId>{};
Set<TileOverlay> tileOverlaysToAdd = <TileOverlay>{};
Set<TileOverlay> tileOverlaysToChange = <TileOverlay>{};
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
case 'map#update':
final Map<String, Object?> arguments =
(call.arguments as Map<Object?, Object?>).cast<String, Object?>();
updateOptions(arguments['options']! as Map<dynamic, dynamic>);
return Future<void>.sync(() {});
case 'markers#update':
updateMarkers(call.arguments as Map<dynamic, dynamic>?);
return Future<void>.sync(() {});
case 'polygons#update':
updatePolygons(call.arguments as Map<dynamic, dynamic>?);
return Future<void>.sync(() {});
case 'polylines#update':
updatePolylines(call.arguments as Map<dynamic, dynamic>?);
return Future<void>.sync(() {});
case 'tileOverlays#update':
updateTileOverlays(Map.castFrom<dynamic, dynamic, String, dynamic>(
call.arguments as Map<dynamic, dynamic>));
return Future<void>.sync(() {});
case 'circles#update':
updateCircles(call.arguments as Map<dynamic, dynamic>?);
return Future<void>.sync(() {});
default:
return Future<void>.sync(() {});
}
}
void updateMarkers(Map<dynamic, dynamic>? markerUpdates) {
if (markerUpdates == null) {
return;
}
markersToAdd = _deserializeMarkers(markerUpdates['markersToAdd']);
markerIdsToRemove = _deserializeMarkerIds(
markerUpdates['markerIdsToRemove'] as List<dynamic>?);
markersToChange = _deserializeMarkers(markerUpdates['markersToChange']);
}
Set<MarkerId> _deserializeMarkerIds(List<dynamic>? markerIds) {
if (markerIds == null) {
return <MarkerId>{};
}
return markerIds
.map((dynamic markerId) => MarkerId(markerId as String))
.toSet();
}
Set<Marker> _deserializeMarkers(dynamic markers) {
if (markers == null) {
return <Marker>{};
}
final List<dynamic> markersData = markers as List<dynamic>;
final Set<Marker> result = <Marker>{};
for (final Map<dynamic, dynamic> markerData
in markersData.cast<Map<dynamic, dynamic>>()) {
final String markerId = markerData['markerId'] as String;
final double alpha = markerData['alpha'] as double;
final bool draggable = markerData['draggable'] as bool;
final bool visible = markerData['visible'] as bool;
final dynamic infoWindowData = markerData['infoWindow'];
InfoWindow infoWindow = InfoWindow.noText;
if (infoWindowData != null) {
final Map<dynamic, dynamic> infoWindowMap =
infoWindowData as Map<dynamic, dynamic>;
infoWindow = InfoWindow(
title: infoWindowMap['title'] as String?,
snippet: infoWindowMap['snippet'] as String?,
);
}
result.add(Marker(
markerId: MarkerId(markerId),
draggable: draggable,
visible: visible,
infoWindow: infoWindow,
alpha: alpha,
));
}
return result;
}
void updatePolygons(Map<dynamic, dynamic>? polygonUpdates) {
if (polygonUpdates == null) {
return;
}
polygonsToAdd = _deserializePolygons(polygonUpdates['polygonsToAdd']);
polygonIdsToRemove = _deserializePolygonIds(
polygonUpdates['polygonIdsToRemove'] as List<dynamic>?);
polygonsToChange = _deserializePolygons(polygonUpdates['polygonsToChange']);
}
Set<PolygonId> _deserializePolygonIds(List<dynamic>? polygonIds) {
if (polygonIds == null) {
return <PolygonId>{};
}
return polygonIds
.map((dynamic polygonId) => PolygonId(polygonId as String))
.toSet();
}
Set<Polygon> _deserializePolygons(dynamic polygons) {
if (polygons == null) {
return <Polygon>{};
}
final List<dynamic> polygonsData = polygons as List<dynamic>;
final Set<Polygon> result = <Polygon>{};
for (final Map<dynamic, dynamic> polygonData
in polygonsData.cast<Map<dynamic, dynamic>>()) {
final String polygonId = polygonData['polygonId'] as String;
final bool visible = polygonData['visible'] as bool;
final bool geodesic = polygonData['geodesic'] as bool;
final List<LatLng> points =
_deserializePoints(polygonData['points'] as List<dynamic>);
final List<List<LatLng>> holes =
_deserializeHoles(polygonData['holes'] as List<dynamic>);
result.add(Polygon(
polygonId: PolygonId(polygonId),
visible: visible,
geodesic: geodesic,
points: points,
holes: holes,
));
}
return result;
}
// Converts a list of points expressed as two-element lists of doubles into
// a list of `LatLng`s. All list items are assumed to be non-null.
List<LatLng> _deserializePoints(List<dynamic> points) {
return points.map<LatLng>((dynamic item) {
final List<Object?> list = item as List<Object?>;
return LatLng(list[0]! as double, list[1]! as double);
}).toList();
}
List<List<LatLng>> _deserializeHoles(List<dynamic> holes) {
return holes.map<List<LatLng>>((dynamic hole) {
return _deserializePoints(hole as List<dynamic>);
}).toList();
}
void updatePolylines(Map<dynamic, dynamic>? polylineUpdates) {
if (polylineUpdates == null) {
return;
}
polylinesToAdd = _deserializePolylines(polylineUpdates['polylinesToAdd']);
polylineIdsToRemove = _deserializePolylineIds(
polylineUpdates['polylineIdsToRemove'] as List<dynamic>?);
polylinesToChange =
_deserializePolylines(polylineUpdates['polylinesToChange']);
}
Set<PolylineId> _deserializePolylineIds(List<dynamic>? polylineIds) {
if (polylineIds == null) {
return <PolylineId>{};
}
return polylineIds
.map((dynamic polylineId) => PolylineId(polylineId as String))
.toSet();
}
Set<Polyline> _deserializePolylines(dynamic polylines) {
if (polylines == null) {
return <Polyline>{};
}
final List<dynamic> polylinesData = polylines as List<dynamic>;
final Set<Polyline> result = <Polyline>{};
for (final Map<dynamic, dynamic> polylineData
in polylinesData.cast<Map<dynamic, dynamic>>()) {
final String polylineId = polylineData['polylineId'] as String;
final bool visible = polylineData['visible'] as bool;
final bool geodesic = polylineData['geodesic'] as bool;
final List<LatLng> points =
_deserializePoints(polylineData['points'] as List<dynamic>);
result.add(Polyline(
polylineId: PolylineId(polylineId),
visible: visible,
geodesic: geodesic,
points: points,
));
}
return result;
}
void updateCircles(Map<dynamic, dynamic>? circleUpdates) {
if (circleUpdates == null) {
return;
}
circlesToAdd = _deserializeCircles(circleUpdates['circlesToAdd']);
circleIdsToRemove = _deserializeCircleIds(
circleUpdates['circleIdsToRemove'] as List<dynamic>?);
circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']);
}
void updateTileOverlays(Map<String, dynamic> updateTileOverlayUpdates) {
final List<Map<dynamic, dynamic>>? tileOverlaysToAddList =
updateTileOverlayUpdates['tileOverlaysToAdd'] != null
? List.castFrom<dynamic, Map<dynamic, dynamic>>(
updateTileOverlayUpdates['tileOverlaysToAdd'] as List<dynamic>)
: null;
final List<String>? tileOverlayIdsToRemoveList =
updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null
? List.castFrom<dynamic, String>(
updateTileOverlayUpdates['tileOverlayIdsToRemove']
as List<dynamic>)
: null;
final List<Map<dynamic, dynamic>>? tileOverlaysToChangeList =
updateTileOverlayUpdates['tileOverlaysToChange'] != null
? List.castFrom<dynamic, Map<dynamic, dynamic>>(
updateTileOverlayUpdates['tileOverlaysToChange']
as List<dynamic>)
: null;
tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList);
tileOverlayIdsToRemove =
_deserializeTileOverlayIds(tileOverlayIdsToRemoveList);
tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList);
}
Set<CircleId> _deserializeCircleIds(List<dynamic>? circleIds) {
if (circleIds == null) {
return <CircleId>{};
}
return circleIds
.map((dynamic circleId) => CircleId(circleId as String))
.toSet();
}
Set<Circle> _deserializeCircles(dynamic circles) {
if (circles == null) {
return <Circle>{};
}
final List<dynamic> circlesData = circles as List<dynamic>;
final Set<Circle> result = <Circle>{};
for (final Map<dynamic, dynamic> circleData
in circlesData.cast<Map<dynamic, dynamic>>()) {
final String circleId = circleData['circleId'] as String;
final bool visible = circleData['visible'] as bool;
final double radius = circleData['radius'] as double;
result.add(Circle(
circleId: CircleId(circleId),
visible: visible,
radius: radius,
));
}
return result;
}
Set<TileOverlayId> _deserializeTileOverlayIds(List<String>? tileOverlayIds) {
if (tileOverlayIds == null || tileOverlayIds.isEmpty) {
return <TileOverlayId>{};
}
return tileOverlayIds
.map((String tileOverlayId) => TileOverlayId(tileOverlayId))
.toSet();
}
Set<TileOverlay> _deserializeTileOverlays(
List<Map<dynamic, dynamic>>? tileOverlays) {
if (tileOverlays == null || tileOverlays.isEmpty) {
return <TileOverlay>{};
}
final Set<TileOverlay> result = <TileOverlay>{};
for (final Map<dynamic, dynamic> tileOverlayData in tileOverlays) {
final String tileOverlayId = tileOverlayData['tileOverlayId'] as String;
final bool fadeIn = tileOverlayData['fadeIn'] as bool;
final double transparency = tileOverlayData['transparency'] as double;
final int zIndex = tileOverlayData['zIndex'] as int;
final bool visible = tileOverlayData['visible'] as bool;
result.add(TileOverlay(
tileOverlayId: TileOverlayId(tileOverlayId),
fadeIn: fadeIn,
transparency: transparency,
zIndex: zIndex,
visible: visible,
));
}
return result;
}
void updateOptions(Map<dynamic, dynamic> options) {
if (options.containsKey('compassEnabled')) {
compassEnabled = options['compassEnabled'] as bool?;
}
if (options.containsKey('mapToolbarEnabled')) {
mapToolbarEnabled = options['mapToolbarEnabled'] as bool?;
}
if (options.containsKey('cameraTargetBounds')) {
final List<dynamic> boundsList =
options['cameraTargetBounds'] as List<dynamic>;
cameraTargetBounds = boundsList[0] == null
? CameraTargetBounds.unbounded
: CameraTargetBounds(LatLngBounds.fromList(boundsList[0]));
}
if (options.containsKey('mapType')) {
mapType = MapType.values[options['mapType'] as int];
}
if (options.containsKey('minMaxZoomPreference')) {
final List<dynamic> minMaxZoomList =
options['minMaxZoomPreference'] as List<dynamic>;
minMaxZoomPreference = MinMaxZoomPreference(
minMaxZoomList[0] as double?, minMaxZoomList[1] as double?);
}
if (options.containsKey('rotateGesturesEnabled')) {
rotateGesturesEnabled = options['rotateGesturesEnabled'] as bool?;
}
if (options.containsKey('scrollGesturesEnabled')) {
scrollGesturesEnabled = options['scrollGesturesEnabled'] as bool?;
}
if (options.containsKey('tiltGesturesEnabled')) {
tiltGesturesEnabled = options['tiltGesturesEnabled'] as bool?;
}
if (options.containsKey('trackCameraPosition')) {
trackCameraPosition = options['trackCameraPosition'] as bool?;
}
if (options.containsKey('zoomGesturesEnabled')) {
zoomGesturesEnabled = options['zoomGesturesEnabled'] as bool?;
}
if (options.containsKey('zoomControlsEnabled')) {
zoomControlsEnabled = options['zoomControlsEnabled'] as bool?;
}
if (options.containsKey('liteModeEnabled')) {
liteModeEnabled = options['liteModeEnabled'] as bool?;
}
if (options.containsKey('myLocationEnabled')) {
myLocationEnabled = options['myLocationEnabled'] as bool?;
}
if (options.containsKey('myLocationButtonEnabled')) {
myLocationButtonEnabled = options['myLocationButtonEnabled'] as bool?;
}
if (options.containsKey('trafficEnabled')) {
trafficEnabled = options['trafficEnabled'] as bool?;
}
if (options.containsKey('buildingsEnabled')) {
buildingsEnabled = options['buildingsEnabled'] as bool?;
}
if (options.containsKey('padding')) {
padding = options['padding'] as List<dynamic>?;
}
}
}
class FakePlatformViewsController {
FakePlatformGoogleMap? lastCreatedView;
Future<dynamic> fakePlatformViewsMethodHandler(MethodCall call) {
switch (call.method) {
case 'create':
final Map<dynamic, dynamic> args =
call.arguments as Map<dynamic, dynamic>;
final Map<dynamic, dynamic> params =
_decodeParams(args['params'] as Uint8List)!;
lastCreatedView = FakePlatformGoogleMap(
args['id'] as int,
params,
);
return Future<int>.sync(() => 1);
default:
return Future<void>.sync(() {});
}
}
void reset() {
lastCreatedView = null;
}
}
Map<dynamic, dynamic>? _decodeParams(Uint8List paramsMessage) {
final ByteBuffer buffer = paramsMessage.buffer;
final ByteData messageBytes = buffer.asByteData(
paramsMessage.offsetInBytes,
paramsMessage.lengthInBytes,
);
return const StandardMessageCodec().decodeMessage(messageBytes)
as Map<dynamic, dynamic>?;
}
/// This allows a value of type T or T? to be treated as a value of type T?.
///
/// We use this so that APIs that have become non-nullable can still be used
/// with `!` and `?` on the stable branch.
T? _ambiguate<T>(T? value) => value;