From 5ebc39d24ba3920a3102620c2f7617d2cada0321 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 14 Jan 2021 13:43:52 -0800 Subject: [PATCH 01/18] platform_interface --- .../CHANGELOG.md | 4 + .../method_channel_google_maps_flutter.dart | 57 +++++++ .../google_maps_flutter_platform.dart | 35 ++++ .../lib/src/types/tile.dart | 39 +++++ .../lib/src/types/tile_overlay.dart | 160 ++++++++++++++++++ .../lib/src/types/tile_overlay_updates.dart | 122 +++++++++++++ .../lib/src/types/tile_provider.dart | 16 ++ .../lib/src/types/types.dart | 5 + .../lib/src/types/utils/tile_overlay.dart | 23 +++ .../google_maps_flutter_platform_test.dart | 3 - .../test/types/tile_overlay_test.dart | 125 ++++++++++++++ .../test/types/tile_overlay_updates_test.dart | 119 +++++++++++++ .../test/types/tile_test.dart | 28 +++ 13 files changed, 733 insertions(+), 3 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_provider.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/tile_overlay.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_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 1e761681e543..4273f596cf39 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 @@ +## 1.2.0 + +* Add TileOverlay support. + ## 1.1.0 * Add support for holes in Polygons. 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 31392354d946..c22278e59776 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 @@ -33,6 +33,10 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return _channels[mapId]; } + // Keep a collection of id -> GetTileCallback + // Every method call passes the int mapId + final Map _getTileCallbacks = {}; + /// Initializes the platform interface with [id]. /// /// This method is called when the plugin is first initialized. @@ -184,6 +188,15 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { LatLng.fromJson(call.arguments['position']), )); break; + case 'tileOverlay#getTile': + final getTileCallback = _getTileCallbacks[mapId]; + final Tile tile = await getTileCallback( + call.arguments['tileOverlayId'], + call.arguments['x'], + call.arguments['y'], + call.arguments['zoom'], + ); + return tile.toJson(); default: throw MissingPluginException(); } @@ -281,6 +294,40 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { ); } + /// Updates tile overlay configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + @override + Future updateTileOverlays( + TileOverlayUpdates tileOverlayUpdates, { + @required int mapId, + }) { + assert(tileOverlayUpdates != null); + return channel(mapId).invokeMethod( + 'tileOverlays#update', + tileOverlayUpdates.toJson(), + ); + } + + /// Clears the tile cache so that all tiles will be requested again from the + /// [TileProvider]. The current tiles from this tile overlay will also be + /// cleared from the map after calling this method. The API maintains a small + /// in-memory cache of tiles. If you want to cache tiles for longer, you + /// should implement an on-disk cache. + @override + Future clearTileCache( + TileOverlayId tileOverlayId, { + @required int mapId, + }) { + return channel(mapId) + .invokeMethod('tileOverlays#clearTileCache', { + 'tileOverlayId': tileOverlayId.value, + }); + } + /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the @@ -451,6 +498,16 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return channel(mapId).invokeMethod('map#takeSnapshot'); } + /// Sets the `MapGetTileCallback` with mapId. + /// + /// `mapId` and `callback` must not be null. + @override + void setGetTileCallback( + {@required int mapId, @required MapGetTileCallback callback}) { + assert(mapId != null && callback != null); + _getTileCallbacks[mapId] = callback; + } + /// This method builds the appropriate platform view where the map /// can be rendered. /// The `mapId` is passed as a parameter from the framework on the 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 a4f487740811..a1a6c87f3289 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 @@ -15,6 +15,10 @@ import 'package:google_maps_flutter_platform_interface/src/method_channel/method import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +/// Callback method for when a [Tile] is requested from a [TileProvider]. +typedef Future MapGetTileCallback( + TileOverlayId tileOverlayId, int x, int y, int zoom); + /// The interface that platform-specific implementations of `google_maps_flutter` must extend. /// /// Avoid `implements` of this interface. Using `implements` makes adding any new @@ -115,6 +119,31 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('updateCircles() has not been implemented.'); } + /// Updates tile overlay configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future updateTileOverlays( + TileOverlayUpdates tileOverlayUpdates, { + @required int mapId, + }) { + throw UnimplementedError('updateTileOverlays() has not been implemented.'); + } + + /// Clears the tile cache so that all tiles will be requested again from the + /// [TileProvider]. The current tiles from this tile overlay will also be + /// cleared from the map after calling this method. The API maintains a small + /// in-memory cache of tiles. If you want to cache tiles for longer, you + /// should implement an on-disk cache. + Future clearTileCache( + TileOverlayId tileOverlayId, { + @required int mapId, + }) { + throw UnimplementedError('clearTileCache() has not been implemented.'); + } + /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the @@ -246,6 +275,12 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('takeSnapshot() has not been implemented.'); } + /// Set the [MapGetTileCallback] for the map, which will be called + /// when a [Tile] is requested for an added [TileProvider]. + void setGetTileCallback({@required int mapId, MapGetTileCallback callback}) { + throw UnimplementedError('onGetTile() has not been implemented.'); + } + // The following are the 11 possible streams of data from the native side // into the plugin diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile.dart new file mode 100644 index 000000000000..3f4ff9fc948e --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile.dart @@ -0,0 +1,39 @@ +// Copyright 2018 The Chromium 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:meta/meta.dart' show immutable; + +/// Contains information about a Tile that is returned by a [TileProvider]. +@immutable +class Tile { + /// Creates an immutable representation of a [Tile] to draw by [TileProvider]. + const Tile(this.width, this.height, this.data); + + /// The width of the image encoded by data in pixels. + final int width; + + /// The height of the image encoded by data in pixels. + final int height; + + /// A byte array containing the image data. + final Uint8List data; + + /// Converts this object to JSON. + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('width', width); + addIfPresent('height', height); + addIfPresent('data', data); + + return json; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart new file mode 100644 index 000000000000..0f9875587ef8 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart @@ -0,0 +1,160 @@ +// Copyright 2018 The Chromium 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:ui' show hashValues; +import 'package:flutter/foundation.dart'; + +import 'types.dart'; +import 'package:meta/meta.dart' show immutable, required; + +/// Uniquely identifies a [TileOverlay] among [GoogleMap] tile overlays. +@immutable +class TileOverlayId { + /// Creates an immutable identifier for a [TileOverlay]. + TileOverlayId(this.value) : assert(value != null); + + /// value of the [TileOverlayId]. + final String value; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is TileOverlayId && other.value == value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() => '${objectRuntimeType(this, 'TileOverlayId')}($value)'; +} + +/// # A set of images which are displayed on top of the base map tiles. +/// +/// These tiles may be transparent, allowing you to add features to existing maps. +/// +/// ## Tile Coordinates +/// +/// Note that the world is projected using the Mercator projection +/// (see [Wikipedia](https://en.wikipedia.org/wiki/Mercator_projection)) with the left (west) side +/// of the map corresponding to -180 degrees of longitude and the right (east) side of the map +/// corresponding to 180 degrees of longitude. To make the map square, the top (north) side of the +/// map corresponds to 85.0511 degrees of latitude and the bottom (south) side of the map +/// corresponds to -85.0511 degrees of latitude. Areas outside this latitude range are not rendered. +/// +/// At each zoom level, the map is divided into tiles and only the tiles that overlap the screen are +/// downloaded and rendered. Each tile is square and the map is divided into tiles as follows: +/// +/// * At zoom level 0, one tile represents the entire world. The coordinates of that tile are +/// (x, y) = (0, 0). +/// * At zoom level 1, the world is divided into 4 tiles arranged in a 2 x 2 grid. +/// * ... +/// * At zoom level N, the world is divided into 4N tiles arranged in a 2N x 2N grid. +/// +/// Note that the minimum zoom level that the camera supports (which can depend on various factors) +/// is GoogleMap.getMinZoomLevel and the maximum zoom level is GoogleMap.getMaxZoomLevel. +/// +/// The coordinates of the tiles are measured from the top left (northwest) corner of the map. +/// At zoom level N, the x values of the tile coordinates range from 0 to 2N - 1 and increase from +/// west to east and the y values range from 0 to 2N - 1 and increase from north to south. +/// +class TileOverlay { + /// Creates an immutable representation of a [TileOverlay] to draw on [GoogleMap]. + const TileOverlay({ + @required this.tileOverlayId, + this.fadeIn = true, + this.tileProvider, + this.transparency = 0.0, + this.zIndex, + this.visible = true, + this.tileSize = 256, + }) : assert(transparency >= 0.0 && transparency <= 1.0); + + /// Uniquely identifies a [TileOverlay]. + final TileOverlayId tileOverlayId; + + /// Whether the tiles should fade in. The default is true. + final bool fadeIn; + + /// The tile provider to use for this tile overlay. + final TileProvider tileProvider; + + /// The transparency of the tile overlay. The default transparency is 0 (opaque). + final double transparency; + + /// The tile overlay's zIndex, i.e., the order in which it will be drawn where + /// overlays with larger values are drawn above those with lower values + final int zIndex; + + /// The visibility for the tile overlay. The default visibility is true. + final bool visible; + + /// Specifies the number of pixels (not points) that the returned tile images will prefer + /// to display as. iOS only. + /// + /// Defaults to 256, which is the traditional size of Google Maps tiles. + /// As an example, an application developer may wish to provide retina tiles (512 pixel edge length) + /// on retina devices, to keep the same number of tiles per view as the default value of 256 + /// would give on a non-retina device. + final int tileSize; + + /// Creates a new [TileOverlay] object whose values are the same as this instance, + /// unless overwritten by the specified parameters. + TileOverlay copyWith({ + TileOverlayId tileOverlayId, + bool fadeInParam, + double transparencyParam, + int zIndexParam, + bool visibleParam, + int tileSizeParam, + }) { + return TileOverlay( + tileOverlayId: tileOverlayId, + fadeIn: fadeInParam ?? fadeIn, + transparency: transparencyParam ?? transparency, + zIndex: zIndexParam ?? zIndex, + visible: visibleParam ?? visible, + tileSize: tileSizeParam ?? tileSize, + ); + } + + /// Converts this object to JSON. + Map toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('tileOverlayId', tileOverlayId.value); + addIfPresent('fadeIn', fadeIn); + addIfPresent('transparency', transparency); + addIfPresent('zIndex', zIndex); + addIfPresent('visible', visible); + addIfPresent('tileSize', tileSize); + + return json; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is TileOverlay + && tileOverlayId == other.tileOverlayId + && fadeIn == other.fadeIn + && transparency == other.transparency + && zIndex == other.zIndex + && visible == other.visible + && tileSize == other.tileSize; + } + + @override + int get hashCode => hashValues(tileOverlayId, fadeIn, transparency, zIndex, visible, tileSize); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart new file mode 100644 index 000000000000..69386dfd731d --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart @@ -0,0 +1,122 @@ +// Copyright 2018 The Chromium 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/foundation.dart' show objectRuntimeType, setEquals; +import 'dart:ui' show hashValues, hashList; + +import 'utils/tile_overlay.dart'; +import 'types.dart'; + +/// Update specification for a set of [TileOverlay]s. +class TileOverlayUpdates { + /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s. + TileOverlayUpdates.from(Set previous, Set current) { + if (previous == null) { + previous = Set.identity(); + } + + if (current == null) { + current = Set.identity(); + } + + final Map previousTileOverlays = + keyTileOverlayId(previous); + final Map currentTileOverlays = + keyTileOverlayId(current); + + final Set prevTileOverlayIds = + previousTileOverlays.keys.toSet(); + final Set currentTileOverlayIds = + currentTileOverlays.keys.toSet(); + + TileOverlay idToCurrentTileOverlay(TileOverlayId id) { + return currentTileOverlays[id]; + } + + _tileOverlayIdsToRemove = + prevTileOverlayIds.difference(currentTileOverlayIds); + + _tileOverlaysToAdd = currentTileOverlayIds + .difference(prevTileOverlayIds) + .map(idToCurrentTileOverlay) + .toSet(); + + // Returns `true` if [current] is not equals to previous one with the + // same id. + bool hasChanged(TileOverlay current) { + final TileOverlay previous = previousTileOverlays[current.tileOverlayId]; + return current != previous; + } + + _tileOverlaysToChange = currentTileOverlayIds + .intersection(prevTileOverlayIds) + .map(idToCurrentTileOverlay) + .where(hasChanged) + .toSet(); + } + + /// Set of TileOverlays to be added in this update. + Set get tileOverlaysToAdd { + return _tileOverlaysToAdd; + } + + Set _tileOverlaysToAdd; + + /// Set of TileOverlayIds to be removed in this update. + Set get tileOverlayIdsToRemove { + return _tileOverlayIdsToRemove; + } + + Set _tileOverlayIdsToRemove; + + /// Set of TileOverlays to be changed in this update. + Set get tileOverlaysToChange { + return _tileOverlaysToChange; + } + + Set _tileOverlaysToChange; + + /// Converts this object to JSON. + Map toJson() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull( + 'tileOverlaysToAdd', serializeTileOverlaySet(_tileOverlaysToAdd)); + addIfNonNull( + 'tileOverlaysToChange', serializeTileOverlaySet(_tileOverlaysToChange)); + addIfNonNull( + 'tileOverlayIdsToRemove', + _tileOverlayIdsToRemove + .map((TileOverlayId m) => m.value) + .toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is TileOverlayUpdates + && setEquals(_tileOverlaysToAdd, other._tileOverlaysToAdd) + && setEquals( + _tileOverlayIdsToRemove, other._tileOverlayIdsToRemove) + && setEquals(_tileOverlaysToChange, other._tileOverlaysToChange); + } + + @override + int get hashCode => hashValues(hashList(_tileOverlaysToAdd), hashList(_tileOverlayIdsToRemove), hashList(_tileOverlaysToChange)); + + @override + String toString() { + return '${objectRuntimeType(this, 'TileOverlayUpdates')}($_tileOverlaysToAdd, $_tileOverlayIdsToRemove, $_tileOverlaysToChange)'; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_provider.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_provider.dart new file mode 100644 index 000000000000..c3c4f807e283 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_provider.dart @@ -0,0 +1,16 @@ +// Copyright 2018 The Chromium 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 'types.dart'; + +/// An interface for a class that provides the tile images for a TileOverlay. +abstract class TileProvider { + /// Stub tile that is used to indicate that no tile exists for a specific tile coordinate. + static const Tile noTile = Tile(-1, -1, null); + + /// Returns the tile to be used for this tile coordinate. + /// + /// See [TileOverlay] for the specification of tile coordinates. + Future getTile(int x, int y, int zoom); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart index e56c3a5dd646..b872f2ff84ed 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart @@ -19,6 +19,10 @@ export 'polygon.dart'; export 'polyline_updates.dart'; export 'polyline.dart'; export 'screen_coordinate.dart'; +export 'tile.dart'; +export 'tile_overlay.dart'; +export 'tile_overlay_updates.dart'; +export 'tile_provider.dart'; export 'ui.dart'; // Export the utils, they're used by the Widget @@ -26,3 +30,4 @@ export 'utils/circle.dart'; export 'utils/marker.dart'; export 'utils/polygon.dart'; export 'utils/polyline.dart'; +export 'utils/tile_overlay.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/tile_overlay.dart new file mode 100644 index 000000000000..0736c836481f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/tile_overlay.dart @@ -0,0 +1,23 @@ +import '../types.dart'; + +/// Converts an [Iterable] of TileOverlay in a Map of TileOverlayId -> TileOverlay. +Map keyTileOverlayId( + Iterable tileOverlays) { + if (tileOverlays == null) { + return {}; + } + return Map.fromEntries(tileOverlays.map( + (TileOverlay tileOverlay) => MapEntry( + tileOverlay.tileOverlayId, tileOverlay))); +} + +/// Converts a Set of TileOverlays into something serializable in JSON. +List> serializeTileOverlaySet( + Set tileOverlays) { + if (tileOverlays == null) { + return null; + } + return tileOverlays + .map>((TileOverlay p) => p.toJson()) + .toList(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart index a003b94d544c..582acea54dd1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart @@ -45,14 +45,11 @@ void main() { log.add(methodCall); }); -// final MethodChannelGoogleMapsFlutter map = MethodChannelGoogleMapsFlutter(0); - tearDown(() { log.clear(); }); test('foo', () async { -// await map.foo(); expect( log, [], diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart new file mode 100644 index 000000000000..ad1e67e43116 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart @@ -0,0 +1,125 @@ +// Copyright 2017 The Chromium 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:ui' show hashValues; +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('tile overlay id tests', () { + test('equality', () async { + final TileOverlayId id1 = TileOverlayId('1'); + final TileOverlayId id2 = TileOverlayId('1'); + final TileOverlayId id3 = TileOverlayId('2'); + expect(id1, id2); + expect(id1, isNot(id3)); + }); + + test('toString', () async { + final TileOverlayId id1 = TileOverlayId('1'); + expect(id1.toString(), 'TileOverlayId(1)'); + }); + }); + + group('tile overlay tests', () { + test('toJson returns correct format', () async { + final TileOverlay tileOverlay = TileOverlay( + tileOverlayId: TileOverlayId('id'), + fadeIn: false, + tileProvider: null, + transparency: 0.1, + zIndex: 1, + visible: false, + tileSize: 128); + final Map json = tileOverlay.toJson(); + expect(json['tileOverlayId'], 'id'); + expect(json['fadeIn'], false); + expect(json['transparency'], moreOrLessEquals(0.1)); + expect(json['zIndex'], 1); + expect(json['visible'], false); + expect(json['tileSize'], 128); + }); + + test('invalid transparency throws', () async { + expect( + () => TileOverlay( + tileOverlayId: TileOverlayId('id1'), transparency: -0.1), + throwsAssertionError); + expect( + () => TileOverlay( + tileOverlayId: TileOverlayId('id2'), transparency: 1.2), + throwsAssertionError); + }); + + test('equality', () async { + final TileOverlay tileOverlay1 = TileOverlay( + tileOverlayId: TileOverlayId('id1'), + fadeIn: false, + tileProvider: null, + transparency: 0.1, + zIndex: 1, + visible: false, + tileSize: 128); + final TileOverlay tileOverlay2 = TileOverlay( + tileOverlayId: TileOverlayId('id1'), + fadeIn: false, + tileProvider: null, + transparency: 0.1, + zIndex: 1, + visible: false, + tileSize: 128); + final TileOverlay tileOverlay3 = TileOverlay( + tileOverlayId: TileOverlayId('id2'), + fadeIn: false, + tileProvider: null, + transparency: 0.1, + zIndex: 1, + visible: false, + tileSize: 128); + expect(tileOverlay1, tileOverlay2); + expect(tileOverlay1, isNot(tileOverlay3)); + }); + + test('hashCode', () async { + TileOverlayId id = TileOverlayId('id1'); + final TileOverlay tileOverlay = TileOverlay( + tileOverlayId: id, + fadeIn: false, + tileProvider: null, + transparency: 0.1, + zIndex: 1, + visible: false, + tileSize: 128); + expect(tileOverlay.hashCode, hashValues(tileOverlay.tileOverlayId, tileOverlay.fadeIn, tileOverlay.transparency, tileOverlay.zIndex, tileOverlay.visible, tileOverlay.tileSize)); + }); + }); + + group('tile overlay utils tests', () { + test('keyTileOverlayId', () async { + final TileOverlay tileOverlay1 = + TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay tileOverlay2 = + TileOverlay(tileOverlayId: TileOverlayId('id2')); + final Map result = + keyTileOverlayId([tileOverlay1, tileOverlay2]); + expect(result[TileOverlayId('id1')], tileOverlay1); + expect(result[TileOverlayId('id2')], tileOverlay2); + }); + + test('serializeTileOverlaySet', () async { + final TileOverlay tileOverlay1 = + TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay tileOverlay2 = + TileOverlay(tileOverlayId: TileOverlayId('id2')); + final Set tileOverlaySet = + Set.from([tileOverlay1, tileOverlay2]); + List> result = + serializeTileOverlaySet(tileOverlaySet); + expect(result[0]['tileOverlayId'], 'id1'); + expect(result[1]['tileOverlayId'], 'id2'); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart new file mode 100644 index 000000000000..1deb5429e1b3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart @@ -0,0 +1,119 @@ +// Copyright 2017 The Chromium 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:ui' show hashValues, hashList; +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('tile overlay updates tests', () { + test('Correctly set toRemove, toAdd and toChange', () async { + final TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); + final TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); + final TileOverlay to3Changed = + TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); + final TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); + final Set previous = Set.from([to1, to2, to3]); + final Set current = + Set.from([to2, to3Changed, to4]); + final TileOverlayUpdates updates = + TileOverlayUpdates.from(previous, current); + + final Set toRemove = + Set.from([TileOverlayId('id1')]); + expect(updates.tileOverlayIdsToRemove, toRemove); + + final Set toAdd = Set.from([to4]); + expect(updates.tileOverlaysToAdd, toAdd); + + final Set toChange = Set.from([to3Changed]); + expect(updates.tileOverlaysToChange, toChange); + }); + + test('toJson', () async { + final TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); + final TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); + final TileOverlay to3Changed = + TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); + final TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); + final Set previous = Set.from([to1, to2, to3]); + final Set current = + Set.from([to2, to3Changed, to4]); + final TileOverlayUpdates updates = + TileOverlayUpdates.from(previous, current); + + final Map json = updates.toJson(); + expect(json, { + 'tileOverlaysToAdd': serializeTileOverlaySet(updates.tileOverlaysToAdd), + 'tileOverlaysToChange': + serializeTileOverlaySet(updates.tileOverlaysToChange), + 'tileOverlayIdsToRemove': updates.tileOverlayIdsToRemove + .map((TileOverlayId m) => m.value) + .toList() + }); + }); + + test('equality', () async { + final TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); + final TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); + final TileOverlay to3Changed = + TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); + final TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); + final Set previous = Set.from([to1, to2, to3]); + final Set current1 = + Set.from([to2, to3Changed, to4]); + final Set current2 = + Set.from([to2, to3Changed, to4]); + final Set current3 = Set.from([to2, to4]); + final TileOverlayUpdates updates1 = + TileOverlayUpdates.from(previous, current1); + final TileOverlayUpdates updates2 = + TileOverlayUpdates.from(previous, current2); + final TileOverlayUpdates updates3 = + TileOverlayUpdates.from(previous, current3); + expect(updates1, updates2); + expect(updates1, isNot(updates3)); + }); + + test('hashCode', () async { + final TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); + final TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); + final TileOverlay to3Changed = + TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); + final TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); + final Set previous = Set.from([to1, to2, to3]); + final Set current = + Set.from([to2, to3Changed, to4]); + final TileOverlayUpdates updates = + TileOverlayUpdates.from(previous, current); + expect( + updates.hashCode, hashValues(hashList(updates.tileOverlaysToAdd), hashList(updates.tileOverlayIdsToRemove), hashList(updates.tileOverlaysToChange))); + }); + + test('toString', () async { + final TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); + final TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); + final TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); + final TileOverlay to3Changed = + TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); + final TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); + final Set previous = Set.from([to1, to2, to3]); + final Set current = + Set.from([to2, to3Changed, to4]); + final TileOverlayUpdates updates = + TileOverlayUpdates.from(previous, current); + expect( + updates.toString(), + 'TileOverlayUpdates(${updates.tileOverlaysToAdd}, ' + '${updates.tileOverlayIdsToRemove}, ' + '${updates.tileOverlaysToChange})'); + }); + }); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_test.dart new file mode 100644 index 000000000000..0be9a7cea8f0 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_test.dart @@ -0,0 +1,28 @@ +// Copyright 2017 The Chromium 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_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('tile tests', () { + test('toJson returns correct format', () async { + final Uint8List data = Uint8List.fromList([0, 1]); + final Tile tile = Tile(100, 200, data); + final Map json = tile.toJson(); + expect(json['width'], 100); + expect(json['height'], 200); + expect(json['data'], data); + }); + + test('toJson returns empty if nothing presents', () async { + final Tile tile = Tile(null, null, null); + final Map json = tile.toJson(); + expect(json.isEmpty, true); + }); + }); +} From e459267272e2a29a4a296c38b6e87acfa6ab7453 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 14 Jan 2021 15:22:22 -0800 Subject: [PATCH 02/18] dart --- .../google_maps_flutter/example/lib/main.dart | 2 + .../example/lib/tile_overlay.dart | 151 +++++++++++++ .../lib/google_maps_flutter.dart | 6 +- .../lib/src/controller.dart | 28 +++ .../lib/src/google_map.dart | 31 +++ .../google_maps_flutter/pubspec.yaml | 3 +- .../test/fake_maps_controllers.dart | 79 +++++++ .../test/tile_overlay_updates_test.dart | 210 ++++++++++++++++++ .../google_maps_flutter_platform.dart | 2 +- 9 files changed, 509 insertions(+), 3 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart index 13763edb8216..ab434be632db 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart @@ -20,6 +20,7 @@ import 'place_polygon.dart'; import 'place_polyline.dart'; import 'scrolling_map.dart'; import 'snapshot.dart'; +import 'tile_overlay.dart'; final List _allPages = [ MapUiPage(), @@ -36,6 +37,7 @@ final List _allPages = [ PaddingPage(), SnapshotPage(), LiteModePage(), + TileOverlayPage(), ]; class MapsDemo extends StatelessWidget { diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart new file mode 100644 index 000000000000..b5a1ba0640aa --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart @@ -0,0 +1,151 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +import 'page.dart'; + +class TileOverlayPage extends GoogleMapExampleAppPage { + TileOverlayPage() : super(const Icon(Icons.map), 'Tile overlay'); + + @override + Widget build(BuildContext context) { + return const TileOverlayBody(); + } +} + +class TileOverlayBody extends StatefulWidget { + const TileOverlayBody(); + + @override + State createState() => TileOverlayBodyState(); +} + +class TileOverlayBodyState extends State { + TileOverlayBodyState(); + + GoogleMapController controller; + TileOverlay _tileOverlay; + + void _onMapCreated(GoogleMapController controller) { + this.controller = controller; + } + + @override + void dispose() { + super.dispose(); + } + + void _removeTileOverlay() { + setState(() { + _tileOverlay = null; + }); + } + + void _addTileOverlay() { + final TileOverlay tileOverlay = TileOverlay( + tileOverlayId: TileOverlayId("tile_overlay_1"), + tileProvider: DebugTileProvider(), + ); + setState(() { + _tileOverlay = tileOverlay; + }); + } + + void _clearTileCache() { + if (_tileOverlay != null && controller != null) { + controller.clearTileCache(_tileOverlay.tileOverlayId); + } + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: SizedBox( + width: 350.0, + height: 300.0, + child: GoogleMap( + initialCameraPosition: const CameraPosition( + target: LatLng(59.935460, 30.325177), + zoom: 7.0, + ), + tileOverlays: + _tileOverlay != null ? {_tileOverlay} : null, + onMapCreated: _onMapCreated, + ), + ), + ), + FlatButton( + child: const Text('Add tile overlay'), + onPressed: _addTileOverlay, + ), + FlatButton( + child: const Text('Remove tile overlay'), + onPressed: _removeTileOverlay, + ), + FlatButton( + child: const Text('Clear tile cache'), + onPressed: _clearTileCache, + ), + ], + ); + } +} + +class DebugTileProvider implements TileProvider { + DebugTileProvider() { + boxPaint.isAntiAlias = true; + boxPaint.color = Colors.blue; + boxPaint.strokeWidth = 2.0; + boxPaint.style = PaintingStyle.stroke; + } + + static const int width = 100; + static const int height = 100; + static final Paint boxPaint = Paint(); + static final TextStyle textStyle = TextStyle( + color: Colors.red, + fontSize: 20, + ); + + @override + Future getTile(int x, int y, int zoom) async { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final TextSpan textSpan = TextSpan( + text: "$x,$y", + style: textStyle, + ); + final TextPainter textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout( + minWidth: 0.0, + maxWidth: width.toDouble(), + ); + final Offset offset = const Offset(0, 0); + textPainter.paint(canvas, offset); + canvas.drawRect( + Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); + final ui.Picture picture = recorder.endRecording(); + final Uint8List byteData = await picture + .toImage(width, height) + .then((ui.Image image) => + image.toByteData(format: ui.ImageByteFormat.png)) + .then((ByteData byteData) => byteData.buffer.asUint8List()); + return Tile(width, height, byteData); + } +} \ No newline at end of file diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart index 682c901f4d4a..703ba63e5ccc 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart @@ -42,7 +42,11 @@ export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf PolygonId, Polyline, PolylineId, - ScreenCoordinate; + ScreenCoordinate, + Tile, + TileOverlayId, + TileOverlay, + TileProvider; part 'src/controller.dart'; part 'src/google_map.dart'; 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 f47b8e57b049..056f142885a7 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 @@ -18,6 +18,8 @@ class GoogleMapController { @required this.mapId, }) : assert(_googleMapsFlutterPlatform != null) { _connectStreams(mapId); + _googleMapsFlutterPlatform.setGetTileCallback( + mapId: mapId, callback: _googleMapState._onGetTile); } /// Initialize control of a [GoogleMap] with [id]. @@ -152,6 +154,32 @@ class GoogleMapController { mapId: mapId); } + /// Updates tile overlays configuration. + /// + /// Change listeners are notified once the update has been made on the + /// platform side. + /// + /// The returned [Future] completes after listeners have been notified. + Future _updateTileOverlays(TileOverlayUpdates tileOverlayUpdates) { + assert(tileOverlayUpdates != null); + return _googleMapsFlutterPlatform.updateTileOverlays(tileOverlayUpdates, + mapId: mapId); + } + + /// Clears the tile cache so that all tiles will be requested again from the + /// [TileProvider]. + /// + /// The current tiles from this tile overlay will also be + /// cleared from the map after calling this method. The API maintains a small + /// in-memory cache of tiles. If you want to cache tiles for longer, you + /// should implement an on-disk cache. + Future clearTileCache(TileOverlayId tileOverlayId) async { + assert(tileOverlayId != null); + return _googleMapsFlutterPlatform.clearTileCache(tileOverlayId, + mapId: mapId); + } + + /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index d7f0f1a4e280..114bd6013599 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -50,6 +50,7 @@ class GoogleMap extends StatefulWidget { this.polylines, this.circles, this.onCameraMoveStarted, + this.tileOverlays, this.onCameraMove, this.onCameraIdle, this.onTap, @@ -120,6 +121,9 @@ class GoogleMap extends StatefulWidget { /// Circles to be placed on the map. final Set circles; + /// Tile overlays to be placed on the map. + final Set tileOverlays; + /// Called when the camera starts moving. /// /// This can be initiated by the following: @@ -220,6 +224,7 @@ class _GoogleMapState extends State { Map _polygons = {}; Map _polylines = {}; Map _circles = {}; + Map _tileOverlays = {}; _GoogleMapOptions _googleMapOptions; @override @@ -232,6 +237,7 @@ class _GoogleMapState extends State { 'polylinesToAdd': serializePolylineSet(widget.polylines), 'circlesToAdd': serializeCircleSet(widget.circles), '_webOnlyMapCreationId': _webOnlyMapCreationId, + 'tileOverlaysToAdd': serializeTileOverlaySet(widget.tileOverlays), }; return _googleMapsFlutterPlatform.buildView( @@ -249,6 +255,7 @@ class _GoogleMapState extends State { _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); _circles = keyByCircleId(widget.circles); + _tileOverlays = keyTileOverlayId(widget.tileOverlays); } @override @@ -266,6 +273,7 @@ class _GoogleMapState extends State { _updatePolygons(); _updatePolylines(); _updateCircles(); + _updateTileOverlays(); } void _updateOptions() async { @@ -313,6 +321,14 @@ class _GoogleMapState extends State { _circles = keyByCircleId(widget.circles); } + void _updateTileOverlays() async { + final GoogleMapController controller = await _controller.future; + // ignore: unawaited_futures + controller._updateTileOverlays(TileOverlayUpdates.from( + _tileOverlays.values.toSet(), widget.tileOverlays)); + _tileOverlays = keyTileOverlayId(widget.tileOverlays); + } + Future onPlatformViewCreated(int id) async { final GoogleMapController controller = await GoogleMapController.init( id, @@ -376,6 +392,21 @@ class _GoogleMapState extends State { widget.onLongPress(position); } } + + // Returns the [Tile] from an added [TileOverlay]. + // + // If the TileOverlay or his TileProvider is not found, + // a [TileProvider.noTile] is returned. + Future _onGetTile( + String tileOverlayIdRaw, int x, int y, int zoom) async { + assert(tileOverlayIdRaw != null); + final TileOverlayId tileOverlayId = TileOverlayId(tileOverlayIdRaw); + final TileOverlay tileOverlay = _tileOverlays[tileOverlayId]; + if (tileOverlay == null || tileOverlay.tileProvider == null) { + return null; + } + return await tileOverlay.tileProvider.getTile(x, y, zoom); + } } /// Configuration options for the GoogleMaps user interface. diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index ef3a06f5862c..4dcd6807d2cb 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -7,7 +7,8 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^1.0.0 - google_maps_flutter_platform_interface: ^1.1.0 + google_maps_flutter_platform_interface: + path: ../google_maps_flutter_platform_interface dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart index 9a849bd94e70..d72ac2ebe656 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart @@ -19,6 +19,7 @@ class FakePlatformGoogleMap { updatePolygons(params); updatePolylines(params); updateCircles(params); + updateTileOverlays(Map.castFrom(params)); } MethodChannel channel; @@ -83,6 +84,12 @@ class FakePlatformGoogleMap { Set circlesToChange; + Set tileOverlayIdsToRemove; + + Set tileOverlaysToAdd; + + Set tileOverlaysToChange; + Future onMethodCall(MethodCall call) { switch (call.method) { case 'map#update': @@ -97,6 +104,10 @@ class FakePlatformGoogleMap { case 'polylines#update': updatePolylines(call.arguments); return Future.sync(() {}); + case 'tileOverlays#update': + updateTileOverlays( + Map.castFrom(call.arguments)); + return Future.sync(() {}); case 'circles#update': updateCircles(call.arguments); return Future.sync(() {}); @@ -292,6 +303,31 @@ class FakePlatformGoogleMap { circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']); } + void updateTileOverlays(Map updateTileOverlayUpdates) { + if (updateTileOverlayUpdates == null) { + return; + } + final List> tileOverlaysToAddList = + updateTileOverlayUpdates['tileOverlaysToAdd'] != null + ? List.castFrom>( + updateTileOverlayUpdates['tileOverlaysToAdd']) + : null; + final List tileOverlayIdsToRemoveList = + updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null + ? List.castFrom( + updateTileOverlayUpdates['tileOverlayIdsToRemove']) + : null; + final List> tileOverlaysToChangeList = + updateTileOverlayUpdates['tileOverlaysToChange'] != null + ? List.castFrom>( + updateTileOverlayUpdates['tileOverlaysToChange']) + : null; + tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList); + tileOverlayIdsToRemove = + _deserializeTileOverlayIds(tileOverlayIdsToRemoveList); + tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList); + } + Set _deserializeCircleIds(List circleIds) { if (circleIds == null) { // TODO(iskakaushik): Remove this when collection literals makes it to stable. @@ -329,6 +365,49 @@ class FakePlatformGoogleMap { return result; } + Set _deserializeTileOverlayIds(List tileOverlayIds) { + if (tileOverlayIds == null || tileOverlayIds.isEmpty) { + // TODO(iskakaushik): Remove this when collection literals makes it to stable. + // https://github.com/flutter/flutter/issues/28312 + // ignore: prefer_collection_literals + return Set(); + } + return tileOverlayIds + .map((String tileOverlayId) => TileOverlayId(tileOverlayId)) + .toSet(); + } + + Set _deserializeTileOverlays( + List> tileOverlays) { + if (tileOverlays == null || tileOverlays.isEmpty) { + // TODO(iskakaushik): Remove this when collection literals makes it to stable. + // https://github.com/flutter/flutter/issues/28312 + // ignore: prefer_collection_literals + return Set(); + } + // TODO(iskakaushik): Remove this when collection literals makes it to stable. + // https://github.com/flutter/flutter/issues/28312 + // ignore: prefer_collection_literals + final Set result = Set(); + for (Map tileOverlayData in tileOverlays) { + final String tileOverlayId = tileOverlayData['tileOverlayId']; + final bool fadeIn = tileOverlayData['fadeIn']; + final double transparency = tileOverlayData['transparency']; + final int zIndex = tileOverlayData['zIndex']; + final bool visible = tileOverlayData['visible']; + + result.add(TileOverlay( + tileOverlayId: TileOverlayId(tileOverlayId), + fadeIn: fadeIn, + transparency: transparency, + zIndex: zIndex, + visible: visible, + )); + } + + return result; + } + void updateOptions(Map options) { if (options.containsKey('compassEnabled')) { compassEnabled = options['compassEnabled']; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart new file mode 100644 index 000000000000..5c9c700c30d1 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart @@ -0,0 +1,210 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +import 'fake_maps_controllers.dart'; + +Set _toSet({TileOverlay t1, TileOverlay t2, TileOverlay t3}) { + final Set res = Set.identity(); + if (t1 != null) { + res.add(t1); + } + if (t2 != null) { + res.add(t2); + } + if (t3 != null) { + res.add(t3); + } + return res; +} + +Widget _mapWithTileOverlays(Set tileOverlays) { + return Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), + tileOverlays: tileOverlays, + ), + ); +} + +void main() { + final FakePlatformViewsController fakePlatformViewsController = + FakePlatformViewsController(); + + setUpAll(() { + SystemChannels.platform_views.setMockMethodCallHandler( + fakePlatformViewsController.fakePlatformViewsMethodHandler); + }); + + setUp(() { + fakePlatformViewsController.reset(); + }); + + testWidgets('Initializing a tile overlay', (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1))); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + expect(platformGoogleMap.tileOverlaysToAdd.length, 1); + + final TileOverlay initializedTileOverlay = + platformGoogleMap.tileOverlaysToAdd.first; + expect(initializedTileOverlay, equals(t1)); + expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); + expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); + }); + + testWidgets("Adding a tile overlay", (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + final TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2")); + + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1))); + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1, t2: t2))); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + expect(platformGoogleMap.tileOverlaysToAdd.length, 1); + + final TileOverlay addedTileOverlay = + platformGoogleMap.tileOverlaysToAdd.first; + expect(addedTileOverlay, equals(t2)); + expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); + + expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); + }); + + testWidgets("Removing a tile overlay", (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1))); + await tester.pumpWidget(_mapWithTileOverlays(null)); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); + expect(platformGoogleMap.tileOverlayIdsToRemove.first, + equals(t1.tileOverlayId)); + + expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); + expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + }); + + testWidgets("Updating a tile overlay", (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + final TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"), zIndex: 10); + + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1))); + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t2))); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + expect(platformGoogleMap.tileOverlaysToChange.length, 1); + expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); + + expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); + expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + }); + + testWidgets("Updating a tile overlay", (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + final TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"), zIndex: 10); + + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1))); + await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t2))); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + expect(platformGoogleMap.tileOverlaysToChange.length, 1); + + final TileOverlay update = platformGoogleMap.tileOverlaysToChange.first; + expect(update, equals(t2)); + expect(update.zIndex, 10); + }); + + testWidgets("Multi Update", (WidgetTester tester) async { + TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2")); + final Set prev = _toSet(t1: t1, t2: t2); + t1 = TileOverlay( + tileOverlayId: TileOverlayId("tile_overlay_1"), visible: false); + t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"), zIndex: 10); + final Set cur = _toSet(t1: t1, t2: t2); + + await tester.pumpWidget(_mapWithTileOverlays(prev)); + await tester.pumpWidget(_mapWithTileOverlays(cur)); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + + expect(platformGoogleMap.tileOverlaysToChange, cur); + expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); + expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + }); + + testWidgets("Multi Update", (WidgetTester tester) async { + TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2")); + final TileOverlay t3 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3")); + final Set prev = _toSet(t2: t2, t3: t3); + + // t1 is added, t2 is updated, t3 is removed. + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"), zIndex: 10); + final Set cur = _toSet(t1: t1, t2: t2); + + await tester.pumpWidget(_mapWithTileOverlays(prev)); + await tester.pumpWidget(_mapWithTileOverlays(cur)); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + + expect(platformGoogleMap.tileOverlaysToChange.length, 1); + expect(platformGoogleMap.tileOverlaysToAdd.length, 1); + expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); + + expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); + expect(platformGoogleMap.tileOverlaysToAdd.first, equals(t1)); + expect(platformGoogleMap.tileOverlayIdsToRemove.first, + equals(t3.tileOverlayId)); + }); + + testWidgets("Partial Update", (WidgetTester tester) async { + final TileOverlay t1 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1")); + final TileOverlay t2 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2")); + TileOverlay t3 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3")); + final Set prev = _toSet(t1: t1, t2: t2, t3: t3); + t3 = + TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3"), zIndex: 10); + final Set cur = _toSet(t1: t1, t2: t2, t3: t3); + + await tester.pumpWidget(_mapWithTileOverlays(prev)); + await tester.pumpWidget(_mapWithTileOverlays(cur)); + + final FakePlatformGoogleMap platformGoogleMap = + fakePlatformViewsController.lastCreatedView; + + expect(platformGoogleMap.tileOverlaysToChange, _toSet(t3: t3)); + expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); + expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + }); +} \ No newline at end of file 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 a1a6c87f3289..a3e3a2a3097b 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 @@ -17,7 +17,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; /// Callback method for when a [Tile] is requested from a [TileProvider]. typedef Future MapGetTileCallback( - TileOverlayId tileOverlayId, int x, int y, int zoom); + String tileOverlayId, int x, int y, int zoom); /// The interface that platform-specific implementations of `google_maps_flutter` must extend. /// From 964a5f6a5a6d80c6ca5ebe15ca0c81b5808d44f3 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 15 Jan 2021 11:58:28 -0800 Subject: [PATCH 03/18] android implemenattion --- .../google_maps_flutter/android/build.gradle | 5 + .../flutter/plugins/googlemaps/Convert.java | 54 +++++++++ .../plugins/googlemaps/GoogleMapBuilder.java | 9 ++ .../googlemaps/GoogleMapController.java | 35 ++++++ .../plugins/googlemaps/GoogleMapFactory.java | 4 + .../googlemaps/GoogleMapOptionsSink.java | 4 + .../googlemaps/TileOverlayBuilder.java | 46 ++++++++ .../googlemaps/TileOverlayController.java | 50 ++++++++ .../plugins/googlemaps/TileOverlaySink.java | 20 ++++ .../googlemaps/TileOverlaysController.java | 109 ++++++++++++++++++ .../googlemaps/TileProviderController.java | 101 ++++++++++++++++ .../google_maps_flutter/example/lib/main.dart | 2 +- .../example/lib/tile_overlay.dart | 2 +- .../lib/src/controller.dart | 1 - .../lib/src/google_map.dart | 3 +- .../test/tile_overlay_updates_test.dart | 2 +- .../lib/src/types/tile_overlay.dart | 17 +-- .../lib/src/types/tile_overlay_updates.dart | 12 +- .../test/types/tile_overlay_test.dart | 10 +- .../test/types/tile_overlay_updates_test.dart | 6 +- 20 files changed, 471 insertions(+), 21 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java create mode 100644 packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java create mode 100644 packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java create mode 100644 packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java create mode 100644 packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java diff --git a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle index a1d7da08a8d9..479c100b8293 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle @@ -39,6 +39,11 @@ android { androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 4108a1d23bb5..f910f962ed15 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -23,6 +23,7 @@ import com.google.android.gms.maps.model.PatternItem; import com.google.android.gms.maps.model.RoundCap; import com.google.android.gms.maps.model.SquareCap; +import com.google.android.gms.maps.model.Tile; import io.flutter.view.FlutterMain; import java.util.ArrayList; import java.util.Arrays; @@ -145,6 +146,13 @@ private static int toInt(Object o) { return ((Number) o).intValue(); } + private static byte[] toByteArray(Object o) { + if (o == null) { + return null; + } + return (byte[]) o; + } + static Object cameraPositionToJson(CameraPosition position) { if (position == null) { return null; @@ -200,6 +208,20 @@ static Object circleIdToJson(String circleId) { return data; } + static Map tileOverlayArgumentsToJson( + String tileOverlayId, int x, int y, int zoom) { + + if (tileOverlayId == null) { + return null; + } + final Map data = new HashMap<>(4); + data.put("tileOverlayId", tileOverlayId); + data.put("x", x); + data.put("y", y); + data.put("zoom", zoom); + return data; + } + static Object latLngToJson(LatLng latLng) { return Arrays.asList(latLng.latitude, latLng.longitude); } @@ -645,4 +667,36 @@ private static Cap toCap(Object o) { throw new IllegalArgumentException("Cannot interpret " + o + " as Cap"); } } + + static String interpretTileOverlayOptions(Map data, TileOverlaySink sink) { + final Object fadeIn = data.get("fadeIn"); + if (fadeIn != null) { + sink.setFadeIn(toBoolean(fadeIn)); + } + final Object transparency = data.get("transparency"); + if (transparency != null) { + sink.setTransparency(toFloat(transparency)); + } + final Object zIndex = data.get("zIndex"); + if (zIndex != null) { + sink.setZIndex(toFloat(zIndex)); + } + final Object visible = data.get("visible"); + if (visible != null) { + sink.setVisible(toBoolean(visible)); + } + final String tileOverlayId = (String) data.get("tileOverlayId"); + if (tileOverlayId == null) { + throw new IllegalArgumentException("tileOverlayId was null"); + } else { + return tileOverlayId; + } + } + + static Tile interpretTile(Map data) { + int width = toInt(data.get("width")); + int height = toInt(data.get("height")); + byte[] dataArray = toByteArray(data.get("data")); + return new Tile(width, height, dataArray); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java index 93a3c3ec9c28..6d5c8c64ae1d 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java @@ -10,6 +10,8 @@ import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLngBounds; import io.flutter.plugin.common.BinaryMessenger; +import java.util.List; +import java.util.Map; class GoogleMapBuilder implements GoogleMapOptionsSink { private final GoogleMapOptions options = new GoogleMapOptions(); @@ -23,6 +25,7 @@ class GoogleMapBuilder implements GoogleMapOptionsSink { private Object initialPolygons; private Object initialPolylines; private Object initialCircles; + private List> initialTileOverlays; private Rect padding = new Rect(0, 0, 0, 0); GoogleMapController build( @@ -44,6 +47,7 @@ GoogleMapController build( controller.setInitialPolylines(initialPolylines); controller.setInitialCircles(initialCircles); controller.setPadding(padding.top, padding.left, padding.bottom, padding.right); + controller.setInitialTileOverlays(initialTileOverlays); return controller; } @@ -165,4 +169,9 @@ public void setInitialPolylines(Object initialPolylines) { public void setInitialCircles(Object initialCircles) { this.initialCircles = initialCircles; } + + @Override + public void setInitialTileOverlays(List> initialTileOverlays) { + this.initialTileOverlays = initialTileOverlays; + } } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index f6b8c3fe1fd1..177f86f17647 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -76,10 +76,12 @@ final class GoogleMapController private final PolygonsController polygonsController; private final PolylinesController polylinesController; private final CirclesController circlesController; + private final TileOverlaysController tileOverlaysController; private List initialMarkers; private List initialPolygons; private List initialPolylines; private List initialCircles; + private List> initialTileOverlays; GoogleMapController( int id, @@ -99,6 +101,7 @@ final class GoogleMapController this.polygonsController = new PolygonsController(methodChannel, density); this.polylinesController = new PolylinesController(methodChannel, density); this.circlesController = new CirclesController(methodChannel, density); + this.tileOverlaysController = new TileOverlaysController(methodChannel); } @Override @@ -140,10 +143,12 @@ public void onMapReady(GoogleMap googleMap) { polygonsController.setGoogleMap(googleMap); polylinesController.setGoogleMap(googleMap); circlesController.setGoogleMap(googleMap); + tileOverlaysController.setGoogleMap(googleMap); updateInitialMarkers(); updateInitialPolygons(); updateInitialPolylines(); updateInitialCircles(); + updateInitialTileOverlays(); } @Override @@ -385,6 +390,24 @@ public void onSnapshotReady(Bitmap bitmap) { result.success(mapStyleResult); break; } + case "tileOverlays#update": + { + Object tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); + tileOverlaysController.addTileOverlays((List>) tileOverlaysToAdd); + Object tileOverlaysToChange = call.argument("tileOverlaysToChange"); + tileOverlaysController.changeTileOverlays((List>) tileOverlaysToChange); + Object tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); + tileOverlaysController.removeTileOverlays((List) tileOverlaysToRemove); + result.success(null); + break; + } + case "tileOverlays#clearTileCache": + { + String tileOverlayId = call.argument("tileOverlayId"); + tileOverlaysController.clearTileCache(tileOverlayId); + result.success(null); + break; + } default: result.notImplemented(); } @@ -732,6 +755,18 @@ private void updateInitialCircles() { circlesController.addCircles(initialCircles); } + @Override + public void setInitialTileOverlays(List> initialTileOverlays) { + this.initialTileOverlays = initialTileOverlays; + if (googleMap != null) { + updateInitialTileOverlays(); + } + } + + private void updateInitialTileOverlays() { + tileOverlaysController.addTileOverlays(initialTileOverlays); + } + @SuppressLint("MissingPermission") private void updateMyLocationSettings() { if (hasLocationPermission()) { diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java index e56adbbb987a..bf9188ffd1b1 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java @@ -10,6 +10,7 @@ import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; +import java.util.List; import java.util.Map; public class GoogleMapFactory extends PlatformViewFactory { @@ -46,6 +47,9 @@ public PlatformView create(Context context, int id, Object args) { if (params.containsKey("circlesToAdd")) { builder.setInitialCircles(params.get("circlesToAdd")); } + if (params.containsKey("tileOverlaysToAdd")) { + builder.setInitialTileOverlays((List>) params.get("tileOverlaysToAdd")); + } return builder.build(id, context, binaryMessenger, lifecycleProvider); } } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java index 9e6fa2a27236..03377d4b760e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java @@ -5,6 +5,8 @@ package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLngBounds; +import java.util.List; +import java.util.Map; /** Receiver of GoogleMap configuration options. */ interface GoogleMapOptionsSink { @@ -51,4 +53,6 @@ interface GoogleMapOptionsSink { void setInitialPolylines(Object initialPolylines); void setInitialCircles(Object initialCircles); + + void setInitialTileOverlays(List> initialTileOverlays); } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java new file mode 100644 index 000000000000..1b5593358536 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java @@ -0,0 +1,46 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import com.google.android.gms.maps.model.TileOverlayOptions; +import com.google.android.gms.maps.model.TileProvider; + +class TileOverlayBuilder implements TileOverlaySink { + + private final TileOverlayOptions tileOverlayOptions; + + TileOverlayBuilder() { + this.tileOverlayOptions = new TileOverlayOptions(); + } + + TileOverlayOptions build() { + return tileOverlayOptions; + } + + @Override + public void setFadeIn(boolean fadeIn) { + tileOverlayOptions.fadeIn(fadeIn); + } + + @Override + public void setTransparency(float transparency) { + tileOverlayOptions.transparency(transparency); + } + + @Override + public void setZIndex(float zIndex) { + tileOverlayOptions.zIndex(zIndex); + } + + @Override + public void setVisible(boolean visible) { + tileOverlayOptions.visible(visible); + } + + @Override + public void setTileProvider(TileProvider tileProvider) { + tileOverlayOptions.tileProvider(tileProvider); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java new file mode 100644 index 000000000000..60da7a4fe212 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java @@ -0,0 +1,50 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import com.google.android.gms.maps.model.TileOverlay; +import com.google.android.gms.maps.model.TileProvider; + +class TileOverlayController implements TileOverlaySink { + + private final TileOverlay tileOverlay; + + TileOverlayController(TileOverlay tileOverlay) { + this.tileOverlay = tileOverlay; + } + + void remove() { + tileOverlay.remove(); + } + + void clearTileCache() { + tileOverlay.clearTileCache(); + } + + @Override + public void setFadeIn(boolean fadeIn) { + tileOverlay.setFadeIn(fadeIn); + } + + @Override + public void setTransparency(float transparency) { + tileOverlay.setTransparency(transparency); + } + + @Override + public void setZIndex(float zIndex) { + tileOverlay.setZIndex(zIndex); + } + + @Override + public void setVisible(boolean visible) { + tileOverlay.setVisible(visible); + } + + @Override + public void setTileProvider(TileProvider tileProvider) { + // You can not change tile provider after creation + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java new file mode 100644 index 000000000000..fd611d7c57f3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java @@ -0,0 +1,20 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import com.google.android.gms.maps.model.TileProvider; + +/** Receiver of TileOverlayOptions configuration. */ +interface TileOverlaySink { + void setFadeIn(boolean fadeIn); + + void setTransparency(float transparency); + + void setZIndex(float zIndex); + + void setVisible(boolean visible); + + void setTileProvider(TileProvider tileProvider); +} diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java new file mode 100644 index 000000000000..60a70c67b877 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java @@ -0,0 +1,109 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.TileOverlay; +import com.google.android.gms.maps.model.TileOverlayOptions; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class TileOverlaysController { + + private final Map tileOverlayIdToController; + private final MethodChannel methodChannel; + private GoogleMap googleMap; + + TileOverlaysController(MethodChannel methodChannel) { + this.tileOverlayIdToController = new HashMap<>(); + this.methodChannel = methodChannel; + } + + void setGoogleMap(GoogleMap googleMap) { + this.googleMap = googleMap; + } + + void addTileOverlays(List> tileOverlaysToAdd) { + if (tileOverlaysToAdd == null) { + return; + } + for (Map tileOverlayToAdd : tileOverlaysToAdd) { + addTileOverlay(tileOverlayToAdd); + } + } + + void changeTileOverlays(List> tileOverlaysToChange) { + if (tileOverlaysToChange == null) { + return; + } + for (Map tileOverlayToChange : tileOverlaysToChange) { + changeTileOverlay(tileOverlayToChange); + } + } + + void removeTileOverlays(List tileOverlayIdsToRemove) { + if (tileOverlayIdsToRemove == null) { + return; + } + for (String tileOverlayId : tileOverlayIdsToRemove) { + if (tileOverlayId == null) { + continue; + } + removeTileOverlay(tileOverlayId); + } + } + + void clearTileCache(String tileOverlayId) { + if (tileOverlayId == null) { + return; + } + TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); + if (tileOverlayController != null) { + tileOverlayController.clearTileCache(); + } + } + + private void addTileOverlay(Map tileOverlayOptions) { + if (tileOverlayOptions == null) { + return; + } + TileOverlayBuilder tileOverlayOptionsBuilder = new TileOverlayBuilder(); + String tileOverlayId = + Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayOptionsBuilder); + TileProviderController tileProviderController = + new TileProviderController(methodChannel, tileOverlayId); + tileOverlayOptionsBuilder.setTileProvider(tileProviderController); + TileOverlayOptions options = tileOverlayOptionsBuilder.build(); + TileOverlay tileOverlay = googleMap.addTileOverlay(options); + TileOverlayController tileOverlayController = new TileOverlayController(tileOverlay); + tileOverlayIdToController.put(tileOverlayId, tileOverlayController); + } + + private void changeTileOverlay(Map tileOverlayOptions) { + if (tileOverlayOptions == null) { + return; + } + String tileOverlayId = getTileOverlayId(tileOverlayOptions); + TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); + if (tileOverlayController != null) { + Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayController); + } + } + + private void removeTileOverlay(String tileOverlayId) { + TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); + if (tileOverlayController != null) { + tileOverlayController.remove(); + tileOverlayIdToController.remove(tileOverlayId); + } + } + + @SuppressWarnings("unchecked") + private static String getTileOverlayId(Map tileOverlay) { + return (String) tileOverlay.get("tileOverlayId"); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java new file mode 100644 index 000000000000..a157597af535 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java @@ -0,0 +1,101 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlemaps; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.android.gms.maps.model.Tile; +import com.google.android.gms.maps.model.TileProvider; +import io.flutter.plugin.common.MethodChannel; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +class TileProviderController implements TileProvider { + + private static final String TAG = "TileProviderController"; + + private final String tileOverlayId; + private final MethodChannel methodChannel; + private final Handler handler = new Handler(Looper.getMainLooper()); + + TileProviderController(MethodChannel methodChannel, String tileOverlayId) { + this.tileOverlayId = tileOverlayId; + this.methodChannel = methodChannel; + } + + @Override + public Tile getTile(final int x, final int y, final int zoom) { + Worker worker = new Worker(x, y, zoom); + return worker.getTile(); + } + + private final class Worker implements MethodChannel.Result { + + private final CountDownLatch countDownLatch = new CountDownLatch(1); + private final int x; + private final int y; + private final int zoom; + private Map result; + + Worker(int x, int y, int zoom) { + this.x = x; + this.y = y; + this.zoom = zoom; + } + + @NonNull + Tile getTile() { + handler.post( + () -> + methodChannel.invokeMethod( + "tileOverlay#getTile", + Convert.tileOverlayArgumentsToJson(tileOverlayId, x, y, zoom), + this)); + try { + // `getTile` must be a synchronized operation. + // Because `methodChannel.invokeMethod` is an sync operation, we use a `countDownLatch` to make the process synchronized. + countDownLatch.await(); + } catch (InterruptedException e) { + Log.e( + TAG, + String.format("countDownLatch: can't get tile: x = %d, y= %d, zoom = %d", x, y, zoom), + e); + return TileProvider.NO_TILE; + } + try { + return Convert.interpretTile(result); + } catch (Exception ex) { + Log.e(TAG, "Can't parse tile data", ex); + return TileProvider.NO_TILE; + } + } + + @Override + public void success(Object data) { + result = (Map) data; + countDownLatch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object data) { + Log.e( + TAG, + String.format( + "Can't get tile: errorCode = %s, errorMessage = %s, date = %s", + errorCode, errorCode, data)); + result = null; + countDownLatch.countDown(); + } + + @Override + public void notImplemented() { + Log.e(TAG, "Can't get tile: notImplemented"); + result = null; + countDownLatch.countDown(); + } + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart index ab434be632db..b795040658e6 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart @@ -37,7 +37,7 @@ final List _allPages = [ PaddingPage(), SnapshotPage(), LiteModePage(), - TileOverlayPage(), + TileOverlayPage(), ]; class MapsDemo extends StatelessWidget { diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart index b5a1ba0640aa..b29bb255ebde 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart @@ -148,4 +148,4 @@ class DebugTileProvider implements TileProvider { .then((ByteData byteData) => byteData.buffer.asUint8List()); return Tile(width, height, byteData); } -} \ No newline at end of file +} 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 056f142885a7..62b7d610399c 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 @@ -179,7 +179,6 @@ class GoogleMapController { mapId: mapId); } - /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 114bd6013599..0e9547685b69 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -224,7 +224,8 @@ class _GoogleMapState extends State { Map _polygons = {}; Map _polylines = {}; Map _circles = {}; - Map _tileOverlays = {}; + Map _tileOverlays = + {}; _GoogleMapOptions _googleMapOptions; @override diff --git a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart index 5c9c700c30d1..b94d4906dec7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart @@ -207,4 +207,4 @@ void main() { expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); -} \ No newline at end of file +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart index 0f9875587ef8..3acce5171392 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart @@ -146,15 +146,16 @@ class TileOverlay { if (other.runtimeType != runtimeType) { return false; } - return other is TileOverlay - && tileOverlayId == other.tileOverlayId - && fadeIn == other.fadeIn - && transparency == other.transparency - && zIndex == other.zIndex - && visible == other.visible - && tileSize == other.tileSize; + return other is TileOverlay && + tileOverlayId == other.tileOverlayId && + fadeIn == other.fadeIn && + transparency == other.transparency && + zIndex == other.zIndex && + visible == other.visible && + tileSize == other.tileSize; } @override - int get hashCode => hashValues(tileOverlayId, fadeIn, transparency, zIndex, visible, tileSize); + int get hashCode => hashValues( + tileOverlayId, fadeIn, transparency, zIndex, visible, tileSize); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart index 69386dfd731d..2910fc37d495 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart @@ -105,15 +105,15 @@ class TileOverlayUpdates { if (other.runtimeType != runtimeType) { return false; } - return other is TileOverlayUpdates - && setEquals(_tileOverlaysToAdd, other._tileOverlaysToAdd) - && setEquals( - _tileOverlayIdsToRemove, other._tileOverlayIdsToRemove) - && setEquals(_tileOverlaysToChange, other._tileOverlaysToChange); + return other is TileOverlayUpdates && + setEquals(_tileOverlaysToAdd, other._tileOverlaysToAdd) && + setEquals(_tileOverlayIdsToRemove, other._tileOverlayIdsToRemove) && + setEquals(_tileOverlaysToChange, other._tileOverlaysToChange); } @override - int get hashCode => hashValues(hashList(_tileOverlaysToAdd), hashList(_tileOverlayIdsToRemove), hashList(_tileOverlaysToChange)); + int get hashCode => hashValues(hashList(_tileOverlaysToAdd), + hashList(_tileOverlayIdsToRemove), hashList(_tileOverlaysToChange)); @override String toString() { diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart index ad1e67e43116..84bbdb43b36a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart @@ -93,7 +93,15 @@ void main() { zIndex: 1, visible: false, tileSize: 128); - expect(tileOverlay.hashCode, hashValues(tileOverlay.tileOverlayId, tileOverlay.fadeIn, tileOverlay.transparency, tileOverlay.zIndex, tileOverlay.visible, tileOverlay.tileSize)); + expect( + tileOverlay.hashCode, + hashValues( + tileOverlay.tileOverlayId, + tileOverlay.fadeIn, + tileOverlay.transparency, + tileOverlay.zIndex, + tileOverlay.visible, + tileOverlay.tileSize)); }); }); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart index 1deb5429e1b3..63215a5a50e2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart @@ -94,7 +94,11 @@ void main() { final TileOverlayUpdates updates = TileOverlayUpdates.from(previous, current); expect( - updates.hashCode, hashValues(hashList(updates.tileOverlaysToAdd), hashList(updates.tileOverlayIdsToRemove), hashList(updates.tileOverlaysToChange))); + updates.hashCode, + hashValues( + hashList(updates.tileOverlaysToAdd), + hashList(updates.tileOverlayIdsToRemove), + hashList(updates.tileOverlaysToChange))); }); test('toString', () async { From d6731ded326fe71f15b552b1f493dfbef8287423 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 15 Jan 2021 13:09:48 -0800 Subject: [PATCH 04/18] integration_tests --- .../googlemaps/GoogleMapController.java | 6 + .../googlemaps/TileOverlayController.java | 12 + .../googlemaps/TileOverlaysController.java | 11 + .../google_map_inspector.dart | 7 + .../integration_test/google_maps_test.dart | 243 ++++++++++++++++++ .../example/lib/tile_overlay.dart | 10 +- 6 files changed, 284 insertions(+), 5 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 177f86f17647..819682fc61ad 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -408,6 +408,12 @@ public void onSnapshotReady(Bitmap bitmap) { result.success(null); break; } + case "map#getTileOverlayInfo": + { + String tileOverlayId = call.argument("tileOverlayId"); + result.success(tileOverlaysController.getTileOverlayInfo(tileOverlayId)); + break; + } default: result.notImplemented(); } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java index 60da7a4fe212..1204bcdaafd7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java @@ -6,6 +6,8 @@ import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileProvider; +import java.util.HashMap; +import java.util.Map; class TileOverlayController implements TileOverlaySink { @@ -23,6 +25,16 @@ void clearTileCache() { tileOverlay.clearTileCache(); } + Map getTileOverlayInfo() { + Map tileOverlayInfo = new HashMap<>(); + tileOverlayInfo.put("fadeIn", tileOverlay.getFadeIn()); + tileOverlayInfo.put("transparency", tileOverlay.getTransparency()); + tileOverlayInfo.put("id", tileOverlay.getId()); + tileOverlayInfo.put("zIndex", tileOverlay.getZIndex()); + tileOverlayInfo.put("visible", tileOverlay.isVisible()); + return tileOverlayInfo; + } + @Override public void setFadeIn(boolean fadeIn) { tileOverlay.setFadeIn(fadeIn); diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java index 60a70c67b877..cd583e247cdd 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java @@ -67,6 +67,17 @@ void clearTileCache(String tileOverlayId) { } } + Map getTileOverlayInfo(String tileOverlayId) { + if (tileOverlayId == null) { + return null; + } + TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); + if (tileOverlayController == null) { + return null; + } + return tileOverlayController.getTileOverlayInfo(); + } + private void addTileOverlay(Map tileOverlayOptions) { if (tileOverlayOptions == null) { return; diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart index 2fcfec15713a..0f2dafb80f70 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart @@ -76,4 +76,11 @@ class GoogleMapInspector { Future takeSnapshot() async { return await _channel.invokeMethod('map#takeSnapshot'); } + + Future> getTileOverlayInfo(String id) async { + return await _channel.invokeMapMethod( + 'map#getTileOverlayInfo', { + 'tileOverlayId': id, + }); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart index 2a5bf80a4578..684d61117676 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:integration_test/integration_test.dart'; import 'package:flutter/material.dart'; @@ -987,4 +988,246 @@ void main() { // TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled. // https://github.com/flutter/flutter/issues/57057 skip: Platform.isAndroid); + + testWidgets( + 'set tileOverlay correctly', + (WidgetTester tester) async { + Completer inspectorCompleter = + Completer(); + final TileOverlay tileOverlay1 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), + zIndex: 2, + visible: true, + transparency: 0.2, + fadeIn: true, + ); + + final TileOverlay tileOverlay2 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_2'), + tileProvider: _DebugTileProvider(), + zIndex: 1, + visible: false, + transparency: 0.3, + fadeIn: false, + ); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: _kInitialCameraPosition, + tileOverlays: {tileOverlay1, tileOverlay2}, + onMapCreated: (GoogleMapController controller) { + final GoogleMapInspector inspector = + // ignore: invalid_use_of_visible_for_testing_member + GoogleMapInspector(controller.channel); + inspectorCompleter.complete(inspector); + }, + ), + ), + ); + await tester.pumpAndSettle(const Duration(seconds: 3)); + + final GoogleMapInspector inspector = await inspectorCompleter.future; + + Map tileOverlayInfo1 = + await inspector.getTileOverlayInfo('tile_overlay_1'); + Map tileOverlayInfo2 = + await inspector.getTileOverlayInfo('tile_overlay_2'); + + expect(tileOverlayInfo1['visible'], true); + expect(tileOverlayInfo1['fadeIn'], true); + expect(tileOverlayInfo1['transparency'], + moreOrLessEquals(0.2, epsilon: 0.001)); + expect(tileOverlayInfo1['zIndex'], 2); + + expect(tileOverlayInfo2['visible'], false); + expect(tileOverlayInfo2['fadeIn'], false); + expect(tileOverlayInfo2['transparency'], + moreOrLessEquals(0.3, epsilon: 0.001)); + expect(tileOverlayInfo2['zIndex'], 1); + }, + ); + + testWidgets( + 'update tileOverlays correctly', + (WidgetTester tester) async { + Completer inspectorCompleter = + Completer(); + final Key key = GlobalKey(); + final TileOverlay tileOverlay1 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), + zIndex: 2, + visible: true, + transparency: 0.2, + fadeIn: true, + ); + + final TileOverlay tileOverlay2 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_2'), + tileProvider: _DebugTileProvider(), + zIndex: 3, + visible: true, + transparency: 0.5, + fadeIn: true, + ); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + tileOverlays: {tileOverlay1, tileOverlay2}, + onMapCreated: (GoogleMapController controller) { + final GoogleMapInspector inspector = + // ignore: invalid_use_of_visible_for_testing_member + GoogleMapInspector(controller.channel); + inspectorCompleter.complete(inspector); + }, + ), + ), + ); + final TileOverlay tileOverlay1New = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), + zIndex: 1, + visible: false, + transparency: 0.3, + fadeIn: false, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + tileOverlays: {tileOverlay1New}, + onMapCreated: (GoogleMapController controller) { + fail("OnMapCreated should get called only once."); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + final GoogleMapInspector inspector = await inspectorCompleter.future; + + Map tileOverlayInfo1 = + await inspector.getTileOverlayInfo('tile_overlay_1'); + Map tileOverlayInfo2 = + await inspector.getTileOverlayInfo('tile_overlay_2'); + + expect(tileOverlayInfo1['visible'], false); + expect(tileOverlayInfo1['fadeIn'], false); + expect(tileOverlayInfo1['transparency'], + moreOrLessEquals(0.3, epsilon: 0.001)); + expect(tileOverlayInfo1['zIndex'], 1); + + expect(tileOverlayInfo2, isNull); + }, + ); + + testWidgets( + 'remove tileOverlays correctly', + (WidgetTester tester) async { + Completer inspectorCompleter = + Completer(); + final Key key = GlobalKey(); + final TileOverlay tileOverlay1 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), + zIndex: 2, + visible: true, + transparency: 0.2, + fadeIn: true, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + tileOverlays: {tileOverlay1}, + onMapCreated: (GoogleMapController controller) { + final GoogleMapInspector inspector = + // ignore: invalid_use_of_visible_for_testing_member + GoogleMapInspector(controller.channel); + inspectorCompleter.complete(inspector); + }, + ), + ), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + onMapCreated: (GoogleMapController controller) { + fail("OnMapCreated should get called only once."); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + final GoogleMapInspector inspector = await inspectorCompleter.future; + Map tileOverlayInfo1 = + await inspector.getTileOverlayInfo('tile_overlay_1'); + + expect(tileOverlayInfo1, isNull); + }, + ); +} + +class _DebugTileProvider implements TileProvider { + _DebugTileProvider() { + boxPaint.isAntiAlias = true; + boxPaint.color = Colors.blue; + boxPaint.strokeWidth = 2.0; + boxPaint.style = PaintingStyle.stroke; + } + + static const int width = 100; + static const int height = 100; + static final Paint boxPaint = Paint(); + static final TextStyle textStyle = TextStyle( + color: Colors.red, + fontSize: 20, + ); + + @override + Future getTile(int x, int y, int zoom) async { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final TextSpan textSpan = TextSpan( + text: "$x,$y", + style: textStyle, + ); + final TextPainter textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout( + minWidth: 0.0, + maxWidth: width.toDouble(), + ); + final Offset offset = const Offset(0, 0); + textPainter.paint(canvas, offset); + canvas.drawRect( + Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); + final ui.Picture picture = recorder.endRecording(); + final Uint8List byteData = await picture + .toImage(width, height) + .then((ui.Image image) => + image.toByteData(format: ui.ImageByteFormat.png)) + .then((ByteData byteData) => byteData.buffer.asUint8List()); + return Tile(width, height, byteData); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart index b29bb255ebde..9469ad5f7aed 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart @@ -51,8 +51,8 @@ class TileOverlayBodyState extends State { void _addTileOverlay() { final TileOverlay tileOverlay = TileOverlay( - tileOverlayId: TileOverlayId("tile_overlay_1"), - tileProvider: DebugTileProvider(), + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), ); setState(() { _tileOverlay = tileOverlay; @@ -104,8 +104,8 @@ class TileOverlayBodyState extends State { } } -class DebugTileProvider implements TileProvider { - DebugTileProvider() { +class _DebugTileProvider implements TileProvider { + _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; @@ -125,7 +125,7 @@ class DebugTileProvider implements TileProvider { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( - text: "$x,$y", + text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( From 7a7bef218fe05682dc883a92f22bb73214dd43c3 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 15 Jan 2021 13:19:01 -0800 Subject: [PATCH 05/18] style fix --- .../java/io/flutter/plugins/googlemaps/Convert.java | 12 ++++-------- .../plugins/googlemaps/GoogleMapController.java | 12 ++++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index f910f962ed15..dffea23e1245 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -146,13 +146,6 @@ private static int toInt(Object o) { return ((Number) o).intValue(); } - private static byte[] toByteArray(Object o) { - if (o == null) { - return null; - } - return (byte[]) o; - } - static Object cameraPositionToJson(CameraPosition position) { if (position == null) { return null; @@ -696,7 +689,10 @@ static String interpretTileOverlayOptions(Map data, TileOverlaySink s static Tile interpretTile(Map data) { int width = toInt(data.get("width")); int height = toInt(data.get("height")); - byte[] dataArray = toByteArray(data.get("data")); + byte[] dataArray = null; + if (data.get("data") != null) { + dataArray = (byte[]) data.get("data"); + } return new Tile(width, height, dataArray); } } diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 819682fc61ad..7db65c56a5e8 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -392,12 +392,12 @@ public void onSnapshotReady(Bitmap bitmap) { } case "tileOverlays#update": { - Object tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); - tileOverlaysController.addTileOverlays((List>) tileOverlaysToAdd); - Object tileOverlaysToChange = call.argument("tileOverlaysToChange"); - tileOverlaysController.changeTileOverlays((List>) tileOverlaysToChange); - Object tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); - tileOverlaysController.removeTileOverlays((List) tileOverlaysToRemove); + List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); + tileOverlaysController.addTileOverlays(tileOverlaysToAdd); + List> tileOverlaysToChange = call.argument("tileOverlaysToChange"); + tileOverlaysController.changeTileOverlays(tileOverlaysToChange); + List tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); + tileOverlaysController.removeTileOverlays(tileOverlaysToRemove); result.success(null); break; } From 040eee692c6603dcb7cc7684c0b7852ec571f0d8 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 19 Jan 2021 12:57:43 -0800 Subject: [PATCH 06/18] ios --- .../integration_test/google_maps_test.dart | 127 +++++----- .../FLTGoogleMapTileOverlayController.h | 42 ++++ .../FLTGoogleMapTileOverlayController.m | 230 ++++++++++++++++++ .../ios/Classes/GoogleMapCircleController.m | 2 +- .../ios/Classes/GoogleMapController.m | 30 +++ 5 files changed, 367 insertions(+), 64 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h create mode 100644 packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart index 684d61117676..dccb6c9e6df7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart @@ -1035,14 +1035,14 @@ void main() { Map tileOverlayInfo2 = await inspector.getTileOverlayInfo('tile_overlay_2'); - expect(tileOverlayInfo1['visible'], true); - expect(tileOverlayInfo1['fadeIn'], true); + expect(tileOverlayInfo1['visible'], isTrue); + expect(tileOverlayInfo1['fadeIn'], isTrue); expect(tileOverlayInfo1['transparency'], moreOrLessEquals(0.2, epsilon: 0.001)); expect(tileOverlayInfo1['zIndex'], 2); - expect(tileOverlayInfo2['visible'], false); - expect(tileOverlayInfo2['fadeIn'], false); + expect(tileOverlayInfo2['visible'], isFalse); + expect(tileOverlayInfo2['fadeIn'], isFalse); expect(tileOverlayInfo2['transparency'], moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo2['zIndex'], 1); @@ -1088,6 +1088,9 @@ void main() { ), ), ); + + final GoogleMapInspector inspector = await inspectorCompleter.future; + final TileOverlay tileOverlay1New = TileOverlay( tileOverlayId: TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), @@ -1105,7 +1108,7 @@ void main() { initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1New}, onMapCreated: (GoogleMapController controller) { - fail("OnMapCreated should get called only once."); + fail('update: OnMapCreated should get called only once.'); }, ), ), @@ -1113,15 +1116,13 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 3)); - final GoogleMapInspector inspector = await inspectorCompleter.future; - Map tileOverlayInfo1 = await inspector.getTileOverlayInfo('tile_overlay_1'); Map tileOverlayInfo2 = await inspector.getTileOverlayInfo('tile_overlay_2'); - expect(tileOverlayInfo1['visible'], false); - expect(tileOverlayInfo1['fadeIn'], false); + expect(tileOverlayInfo1['visible'], isFalse); + expect(tileOverlayInfo1['fadeIn'], isFalse); expect(tileOverlayInfo1['transparency'], moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo1['zIndex'], 1); @@ -1130,60 +1131,60 @@ void main() { }, ); - testWidgets( - 'remove tileOverlays correctly', - (WidgetTester tester) async { - Completer inspectorCompleter = - Completer(); - final Key key = GlobalKey(); - final TileOverlay tileOverlay1 = TileOverlay( - tileOverlayId: TileOverlayId('tile_overlay_1'), - tileProvider: _DebugTileProvider(), - zIndex: 2, - visible: true, - transparency: 0.2, - fadeIn: true, - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - tileOverlays: {tileOverlay1}, - onMapCreated: (GoogleMapController controller) { - final GoogleMapInspector inspector = - // ignore: invalid_use_of_visible_for_testing_member - GoogleMapInspector(controller.channel); - inspectorCompleter.complete(inspector); - }, - ), - ), - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - fail("OnMapCreated should get called only once."); - }, - ), - ), - ); - - await tester.pumpAndSettle(const Duration(seconds: 3)); - - final GoogleMapInspector inspector = await inspectorCompleter.future; - Map tileOverlayInfo1 = - await inspector.getTileOverlayInfo('tile_overlay_1'); - - expect(tileOverlayInfo1, isNull); - }, - ); + // testWidgets( + // 'remove tileOverlays correctly', + // (WidgetTester tester) async { + // Completer inspectorCompleter = + // Completer(); + // final Key key = GlobalKey(); + // final TileOverlay tileOverlay1 = TileOverlay( + // tileOverlayId: TileOverlayId('tile_overlay_1'), + // tileProvider: _DebugTileProvider(), + // zIndex: 2, + // visible: true, + // transparency: 0.2, + // fadeIn: true, + // ); + + // await tester.pumpWidget( + // Directionality( + // textDirection: TextDirection.ltr, + // child: GoogleMap( + // key: key, + // initialCameraPosition: _kInitialCameraPosition, + // tileOverlays: {tileOverlay1}, + // onMapCreated: (GoogleMapController controller) { + // final GoogleMapInspector inspector = + // // ignore: invalid_use_of_visible_for_testing_member + // GoogleMapInspector(controller.channel); + // inspectorCompleter.complete(inspector); + // }, + // ), + // ), + // ); + + // final GoogleMapInspector inspector = await inspectorCompleter.future; + + // await tester.pumpWidget( + // Directionality( + // textDirection: TextDirection.ltr, + // child: GoogleMap( + // key: key, + // initialCameraPosition: _kInitialCameraPosition, + // onMapCreated: (GoogleMapController controller) { + // fail('OnMapCreated should get called only once.'); + // }, + // ), + // ), + // ); + + // await tester.pumpAndSettle(const Duration(seconds: 3)); + // Map tileOverlayInfo1 = + // await inspector.getTileOverlayInfo('tile_overlay_1'); + + // expect(tileOverlayInfo1, isNull); + // }, + // ); } class _DebugTileProvider implements TileProvider { diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h new file mode 100644 index 000000000000..90b80bc634f1 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h @@ -0,0 +1,42 @@ +// Copyright 2018 The Chromium 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 +#import + +// Defines map UI options writable from Flutter. +@protocol FLTGoogleMapTileOverlayOptionsSink +- (void)setFadeIn:(BOOL)fadeIn; +- (void)setTransparency:(float)transparency; +- (void)setZIndex:(int)zIndex; +- (void)setVisible:(BOOL)visible; +- (void)setTileSize:(NSInteger)tileSize; +@end + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTGoogleMapTileOverlayController : NSObject +- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView; +- (void)removeTileOverlay; +- (void)clearTileCache; +- (NSDictionary *)getTileOverlayInfo; +@end + +@interface FLTTileProviderController : GMSTileLayer +@property(copy, nonatomic, readonly) NSString *tileOverlayId; +- (instancetype)init:(FlutterMethodChannel *)methodChannel tileOverlayId:(NSString *)tileOverlayId; +@end + +@interface FLTTileOverlaysController : NSObject +- (instancetype)init:(FlutterMethodChannel *)methodChannel + mapView:(GMSMapView *)mapView + registrar:(NSObject *)registrar; +- (void)addTileOverlays:(NSArray *)tileOverlaysToAdd; +- (void)changeTileOverlays:(NSArray *)tileOverlaysToChange; +- (void)removeTileOverlayIds:(NSArray *)tileOverlayIdsToRemove; +- (void)clearTileCache:(NSString *)tileOverlayId; +- (nullable NSDictionary *)getTileOverlayInfo:(NSString *)tileverlayId; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m new file mode 100644 index 000000000000..832174c36923 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m @@ -0,0 +1,230 @@ +// Copyright 2018 The Chromium 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 "FLTGoogleMapTileOverlayController.h" +#import "JsonConversions.h" + +static void InterpretTileOverlayOptions(NSDictionary* data, + id sink, + NSObject* registrar) { + NSNumber* visible = data[@"visible"]; + if (visible != nil) { + [sink setVisible:visible.boolValue]; + } + + NSNumber* transparency = data[@"transparency"]; + if (transparency != nil) { + [sink setTransparency:transparency.floatValue]; + } + + NSNumber* zIndex = data[@"zIndex"]; + if (zIndex != nil) { + [sink setZIndex:zIndex.intValue]; + } + + NSNumber* fadeIn = data[@"fadeIn"]; + if (fadeIn != nil) { + [sink setFadeIn:fadeIn.boolValue]; + } + + NSNumber* tileSize = data[@"tileSize"]; + if (tileSize != nil) { + [sink setTileSize:tileSize.integerValue]; + } +} + +@interface FLTGoogleMapTileOverlayController () + +@property(strong, nonatomic) GMSTileLayer* layer; +@property(weak, nonatomic) GMSMapView* mapView; + +@end + +@implementation FLTGoogleMapTileOverlayController + +- (instancetype)initWithTileLayer:(GMSTileLayer*)tileLayer mapView:(GMSMapView*)mapView { + self = [super init]; + if (self) { + self.layer = tileLayer; + self.mapView = mapView; + } + return self; +} + +- (void)removeTileOverlay { + self.layer.map = nil; +} + +- (void)clearTileCache { + [self.layer clearTileCache]; +} + +- (NSDictionary*)getTileOverlayInfo { + NSMutableDictionary* info = [[NSMutableDictionary alloc] init]; + BOOL visible = self.layer.map != nil; + info[@"visible"] = @(visible); + info[@"fadeIn"] = @(self.layer.fadeIn); + float transparency = 1.0 - self.layer.opacity; + info[@"transparency"] = @(transparency); + info[@"zIndex"] = @(self.layer.zIndex); + return info; +} + +#pragma mark - FLTGoogleMapTileOverlayOptionsSink methods + +- (void)setFadeIn:(BOOL)fadeIn { + self.layer.fadeIn = fadeIn; +} + +- (void)setTransparency:(float)transparency { + float opacity = 1.0 - transparency; + self.layer.opacity = opacity; +} + +- (void)setVisible:(BOOL)visible { + self.layer.map = visible ? self.mapView : nil; +} + +- (void)setZIndex:(int)zIndex { + self.layer.zIndex = zIndex; +} + +- (void)setTileSize:(NSInteger)tileSize { + self.layer.tileSize = tileSize; +} +@end + +@interface FLTTileProviderController () + +@property(weak, nonatomic) FlutterMethodChannel* methodChannel; +@property(copy, nonatomic, readwrite) NSString* tileOverlayId; + +@end + +@implementation FLTTileProviderController + +- (instancetype)init:(FlutterMethodChannel*)methodChannel tileOverlayId:(NSString*)tileOverlayId { + self = [super init]; + if (self) { + self.methodChannel = methodChannel; + self.tileOverlayId = tileOverlayId; + } + return self; +} + +#pragma mark - GMSTileLayer method + +- (void)requestTileForX:(NSUInteger)x + y:(NSUInteger)y + zoom:(NSUInteger)zoom + receiver:(id)receiver { + [self.methodChannel + invokeMethod:@"tileOverlay#getTile" + arguments:@{ + @"tileOverlayId" : self.tileOverlayId, + @"x" : @(x), + @"y" : @(y), + @"zoom" : @(zoom) + } + result:^(id _Nullable result) { + UIImage* tileImage; + if ([result isKindOfClass:[NSDictionary class]]) { + FlutterStandardTypedData* typedData = (FlutterStandardTypedData*)result[@"data"]; + tileImage = [UIImage imageWithData:typedData.data]; + } else { + if ([result isKindOfClass:[FlutterError class]]) { + FlutterError* error = (FlutterError*)result; + NSLog(@"Can't get tile: errorCode = %@, errorMessage = %@, details = %@", + [error code], [error message], [error details]); + } + if ([result isKindOfClass:[FlutterMethodNotImplemented class]]) { + NSLog(@"Can't get tile: notImplemented"); + } + tileImage = kGMSTileLayerNoTile; + } + + [receiver receiveTileWithX:x y:y zoom:zoom image:tileImage]; + }]; +} + +@end + +@interface FLTTileOverlaysController () + +@property(strong, nonatomic) NSMutableDictionary* tileOverlayIdToController; +@property(weak, nonatomic) FlutterMethodChannel* methodChannel; +@property(weak, nonatomic) NSObject* registrar; +@property(weak, nonatomic) GMSMapView* mapView; + +@end + +@implementation FLTTileOverlaysController + +- (instancetype)init:(FlutterMethodChannel*)methodChannel + mapView:(GMSMapView*)mapView + registrar:(NSObject*)registrar { + self = [super init]; + if (self) { + self.methodChannel = methodChannel; + self.mapView = mapView; + self.tileOverlayIdToController = [[NSMutableDictionary alloc] init]; + self.registrar = registrar; + } + return self; +} + +- (void)addTileOverlays:(NSArray*)tileOverlaysToAdd { + for (NSDictionary* tileOverlay in tileOverlaysToAdd) { + NSString* tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay]; + FLTTileProviderController* tileProvider = + [[FLTTileProviderController alloc] init:self.methodChannel tileOverlayId:tileOverlayId]; + FLTGoogleMapTileOverlayController* controller = + [[FLTGoogleMapTileOverlayController alloc] initWithTileLayer:tileProvider + mapView:self.mapView]; + InterpretTileOverlayOptions(tileOverlay, controller, self.registrar); + self.tileOverlayIdToController[tileOverlayId] = controller; + } +} + +- (void)changeTileOverlays:(NSArray*)tileOverlaysToChange { + for (NSDictionary* tileOverlay in tileOverlaysToChange) { + NSString* tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay]; + FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId]; + if (!controller) { + continue; + } + InterpretTileOverlayOptions(tileOverlay, controller, self.registrar); + } +} +- (void)removeTileOverlayIds:(NSArray*)tileOverlayIdsToRemove { + for (NSString* tileOverlayId in tileOverlayIdsToRemove) { + FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId]; + if (!controller) { + continue; + } + [controller removeTileOverlay]; + [self.tileOverlayIdToController removeObjectForKey:tileOverlayId]; + } +} + +- (void)clearTileCache:(NSString*)tileOverlayId { + FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId]; + if (!controller) { + return; + } + [controller clearTileCache]; +} + +- (nullable NSDictionary*)getTileOverlayInfo:(NSString*)tileverlayId { + if (self.tileOverlayIdToController[tileverlayId] == nil) { + return nil; + } + return [self.tileOverlayIdToController[tileverlayId] getTileOverlayInfo]; +} + ++ (NSString*)getTileOverlayId:(NSDictionary*)tileOverlay { + return tileOverlay[@"tileOverlayId"]; +} + +@end diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m index 6688d4d57695..cfdf52590527 100644 --- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m @@ -35,7 +35,7 @@ - (void)setConsumeTapEvents:(BOOL)consumes { - (void)setVisible:(BOOL)visible { _circle.map = visible ? _mapView : nil; } -- (void)setZIndex:(int)zIndex { +- (void)setZIndex:(NSInteger)zIndex { _circle.zIndex = zIndex; } - (void)setCenter:(CLLocationCoordinate2D)center { diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m index 321ddd318536..749ce9e84a4e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "GoogleMapController.h" +#import "FLTGoogleMapTileOverlayController.h" #import "JsonConversions.h" #pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations. @@ -55,6 +56,7 @@ @implementation FLTGoogleMapController { FLTPolygonsController* _polygonsController; FLTPolylinesController* _polylinesController; FLTCirclesController* _circlesController; + FLTTileOverlaysController* _tileOverlaysController; } - (instancetype)initWithFrame:(CGRect)frame @@ -94,6 +96,9 @@ - (instancetype)initWithFrame:(CGRect)frame _circlesController = [[FLTCirclesController alloc] init:_channel mapView:_mapView registrar:registrar]; + _tileOverlaysController = [[FLTTileOverlaysController alloc] init:_channel + mapView:_mapView + registrar:registrar]; id markersToAdd = args[@"markersToAdd"]; if ([markersToAdd isKindOfClass:[NSArray class]]) { [_markersController addMarkers:markersToAdd]; @@ -110,6 +115,10 @@ - (instancetype)initWithFrame:(CGRect)frame if ([circlesToAdd isKindOfClass:[NSArray class]]) { [_circlesController addCircles:circlesToAdd]; } + id tileOverlaysToAdd = args[@"tileOverlaysToAdd"]; + if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) { + [_tileOverlaysController addTileOverlays:tileOverlaysToAdd]; + } } return self; } @@ -298,6 +307,24 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [_circlesController removeCircleIds:circleIdsToRemove]; } result(nil); + } else if ([call.method isEqualToString:@"tileOverlays#update"]) { + id tileOverlaysToAdd = call.arguments[@"tileOverlaysToAdd"]; + if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) { + [_tileOverlaysController addTileOverlays:tileOverlaysToAdd]; + } + id tileOverlaysToChange = call.arguments[@"tileOverlaysToChange"]; + if ([tileOverlaysToChange isKindOfClass:[NSArray class]]) { + [_tileOverlaysController changeTileOverlays:tileOverlaysToChange]; + } + id tileOverlayIdsToRemove = call.arguments[@"tileOverlayIdsToRemove"]; + if ([tileOverlayIdsToRemove isKindOfClass:[NSArray class]]) { + [_tileOverlaysController removeTileOverlayIds:tileOverlayIdsToRemove]; + } + result(nil); + } else if ([call.method isEqualToString:@"tileOverlays#clearTileCache"]) { + id rawTileOverlayId = call.arguments[@"tileOverlayId"]; + [_tileOverlaysController clearTileCache:rawTileOverlayId]; + result(nil); } else if ([call.method isEqualToString:@"map#isCompassEnabled"]) { NSNumber* isCompassEnabled = @(_mapView.settings.compassButton); result(isCompassEnabled); @@ -341,6 +368,9 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else { result(@[ @(NO), error ]); } + } else if ([call.method isEqualToString:@"map#getTileOverlayInfo"]) { + NSString* rawTileOverlayId = call.arguments[@"tileOverlayId"]; + result([_tileOverlaysController getTileOverlayInfo:rawTileOverlayId]); } else { result(FlutterMethodNotImplemented); } From 03390c23e708a423e2a7d72636a90ca5df7b2688 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 19 Jan 2021 13:37:07 -0800 Subject: [PATCH 07/18] fix no tile return value --- .../integration_test/google_maps_test.dart | 108 +++++++++--------- .../example/lib/tile_overlay.dart | 6 +- .../FLTGoogleMapTileOverlayController.m | 6 +- .../lib/src/google_map.dart | 4 +- 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart index dccb6c9e6df7..557337f0c025 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart @@ -1131,60 +1131,60 @@ void main() { }, ); - // testWidgets( - // 'remove tileOverlays correctly', - // (WidgetTester tester) async { - // Completer inspectorCompleter = - // Completer(); - // final Key key = GlobalKey(); - // final TileOverlay tileOverlay1 = TileOverlay( - // tileOverlayId: TileOverlayId('tile_overlay_1'), - // tileProvider: _DebugTileProvider(), - // zIndex: 2, - // visible: true, - // transparency: 0.2, - // fadeIn: true, - // ); - - // await tester.pumpWidget( - // Directionality( - // textDirection: TextDirection.ltr, - // child: GoogleMap( - // key: key, - // initialCameraPosition: _kInitialCameraPosition, - // tileOverlays: {tileOverlay1}, - // onMapCreated: (GoogleMapController controller) { - // final GoogleMapInspector inspector = - // // ignore: invalid_use_of_visible_for_testing_member - // GoogleMapInspector(controller.channel); - // inspectorCompleter.complete(inspector); - // }, - // ), - // ), - // ); - - // final GoogleMapInspector inspector = await inspectorCompleter.future; - - // await tester.pumpWidget( - // Directionality( - // textDirection: TextDirection.ltr, - // child: GoogleMap( - // key: key, - // initialCameraPosition: _kInitialCameraPosition, - // onMapCreated: (GoogleMapController controller) { - // fail('OnMapCreated should get called only once.'); - // }, - // ), - // ), - // ); - - // await tester.pumpAndSettle(const Duration(seconds: 3)); - // Map tileOverlayInfo1 = - // await inspector.getTileOverlayInfo('tile_overlay_1'); - - // expect(tileOverlayInfo1, isNull); - // }, - // ); + testWidgets( + 'remove tileOverlays correctly', + (WidgetTester tester) async { + Completer inspectorCompleter = + Completer(); + final Key key = GlobalKey(); + final TileOverlay tileOverlay1 = TileOverlay( + tileOverlayId: TileOverlayId('tile_overlay_1'), + tileProvider: _DebugTileProvider(), + zIndex: 2, + visible: true, + transparency: 0.2, + fadeIn: true, + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + tileOverlays: {tileOverlay1}, + onMapCreated: (GoogleMapController controller) { + final GoogleMapInspector inspector = + // ignore: invalid_use_of_visible_for_testing_member + GoogleMapInspector(controller.channel); + inspectorCompleter.complete(inspector); + }, + ), + ), + ); + + final GoogleMapInspector inspector = await inspectorCompleter.future; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + key: key, + initialCameraPosition: _kInitialCameraPosition, + onMapCreated: (GoogleMapController controller) { + fail('OnMapCreated should get called only once.'); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 3)); + Map tileOverlayInfo1 = + await inspector.getTileOverlayInfo('tile_overlay_1'); + + expect(tileOverlayInfo1, isNull); + }, + ); } class _DebugTileProvider implements TileProvider { diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart index 9469ad5f7aed..354fa06a7ab7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart @@ -87,15 +87,15 @@ class TileOverlayBodyState extends State { ), ), ), - FlatButton( + TextButton( child: const Text('Add tile overlay'), onPressed: _addTileOverlay, ), - FlatButton( + TextButton( child: const Text('Remove tile overlay'), onPressed: _removeTileOverlay, ), - FlatButton( + TextButton( child: const Text('Clear tile cache'), onPressed: _clearTileCache, ), diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m index 832174c36923..7fbd7c53b90a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m @@ -131,7 +131,11 @@ - (void)requestTileForX:(NSUInteger)x UIImage* tileImage; if ([result isKindOfClass:[NSDictionary class]]) { FlutterStandardTypedData* typedData = (FlutterStandardTypedData*)result[@"data"]; - tileImage = [UIImage imageWithData:typedData.data]; + if (typedData == nil) { + tileImage = kGMSTileLayerNoTile; + } else { + tileImage = [UIImage imageWithData:typedData.data]; + } } else { if ([result isKindOfClass:[FlutterError class]]) { FlutterError* error = (FlutterError*)result; diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 0e9547685b69..e1e8c3dbe6a0 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -396,7 +396,7 @@ class _GoogleMapState extends State { // Returns the [Tile] from an added [TileOverlay]. // - // If the TileOverlay or his TileProvider is not found, + // If the TileOverlay or its TileProvider is not found, // a [TileProvider.noTile] is returned. Future _onGetTile( String tileOverlayIdRaw, int x, int y, int zoom) async { @@ -404,7 +404,7 @@ class _GoogleMapState extends State { final TileOverlayId tileOverlayId = TileOverlayId(tileOverlayIdRaw); final TileOverlay tileOverlay = _tileOverlays[tileOverlayId]; if (tileOverlay == null || tileOverlay.tileProvider == null) { - return null; + return TileProvider.noTile; } return await tileOverlay.tileProvider.getTile(x, y, zoom); } From 5a34d941740321f6675cd271dfe91d774147ce37 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 21 Jan 2021 17:21:06 -0800 Subject: [PATCH 08/18] fix merge issue --- .../method_channel_google_maps_flutter.dart | 10 ---------- .../google_maps_flutter_platform.dart | 10 ---------- .../lib/src/types/types.dart | 1 - 3 files changed, 21 deletions(-) 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 578c245190c3..0ebfad5b5137 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 @@ -512,16 +512,6 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { return channel(mapId).invokeMethod('map#takeSnapshot'); } - /// Sets the `MapGetTileCallback` with mapId. - /// - /// `mapId` and `callback` must not be null. - @override - void setGetTileCallback( - {@required int mapId, @required MapGetTileCallback callback}) { - assert(mapId != null && callback != null); - _getTileCallbacks[mapId] = callback; - } - /// This method builds the appropriate platform view where the map /// can be rendered. /// The `mapId` is passed as a parameter from the framework on the 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 96a373388e7e..b9d3fecc0d0a 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 @@ -15,10 +15,6 @@ import 'package:google_maps_flutter_platform_interface/src/method_channel/method import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -/// Callback method for when a [Tile] is requested from a [TileProvider]. -typedef Future MapGetTileCallback( - String tileOverlayId, int x, int y, int zoom); - /// The interface that platform-specific implementations of `google_maps_flutter` must extend. /// /// Avoid `implements` of this interface. Using `implements` makes adding any new @@ -277,12 +273,6 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('takeSnapshot() has not been implemented.'); } - /// Set the [MapGetTileCallback] for the map, which will be called - /// when a [Tile] is requested for an added [TileProvider]. - void setGetTileCallback({@required int mapId, MapGetTileCallback callback}) { - throw UnimplementedError('onGetTile() has not been implemented.'); - } - // The following are the 11 possible streams of data from the native side // into the plugin diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart index 3e2002f80ae3..e4b5c0bc3ab2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart @@ -29,4 +29,3 @@ export 'utils/circle.dart'; export 'utils/marker.dart'; export 'utils/polygon.dart'; export 'utils/polyline.dart'; -export 'utils/tile_overlay.dart'; From 83e10f9507cbb35d651740e1bf1d5da1630830d0 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 21 Jan 2021 18:06:31 -0800 Subject: [PATCH 09/18] fix with new platform interface api --- .../lib/src/controller.dart | 8 +++---- .../lib/src/google_map.dart | 22 +------------------ 2 files changed, 4 insertions(+), 26 deletions(-) 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 62b7d610399c..cd4d5c6df9c7 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 @@ -18,8 +18,7 @@ class GoogleMapController { @required this.mapId, }) : assert(_googleMapsFlutterPlatform != null) { _connectStreams(mapId); - _googleMapsFlutterPlatform.setGetTileCallback( - mapId: mapId, callback: _googleMapState._onGetTile); + _googleMapsFlutterPlatform.updateTileOverlays(mapId: mapId, newTileOverlays: _googleMapState.widget.tileOverlays); } /// Initialize control of a [GoogleMap] with [id]. @@ -160,9 +159,8 @@ class GoogleMapController { /// platform side. /// /// The returned [Future] completes after listeners have been notified. - Future _updateTileOverlays(TileOverlayUpdates tileOverlayUpdates) { - assert(tileOverlayUpdates != null); - return _googleMapsFlutterPlatform.updateTileOverlays(tileOverlayUpdates, + Future _updateTileOverlays(Set newTileOverlays) { + return _googleMapsFlutterPlatform.updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index e1e8c3dbe6a0..d60b67e5deca 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -224,8 +224,6 @@ class _GoogleMapState extends State { Map _polygons = {}; Map _polylines = {}; Map _circles = {}; - Map _tileOverlays = - {}; _GoogleMapOptions _googleMapOptions; @override @@ -256,7 +254,6 @@ class _GoogleMapState extends State { _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); _circles = keyByCircleId(widget.circles); - _tileOverlays = keyTileOverlayId(widget.tileOverlays); } @override @@ -325,9 +322,7 @@ class _GoogleMapState extends State { void _updateTileOverlays() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures - controller._updateTileOverlays(TileOverlayUpdates.from( - _tileOverlays.values.toSet(), widget.tileOverlays)); - _tileOverlays = keyTileOverlayId(widget.tileOverlays); + controller._updateTileOverlays(widget.tileOverlays); } Future onPlatformViewCreated(int id) async { @@ -393,21 +388,6 @@ class _GoogleMapState extends State { widget.onLongPress(position); } } - - // Returns the [Tile] from an added [TileOverlay]. - // - // If the TileOverlay or its TileProvider is not found, - // a [TileProvider.noTile] is returned. - Future _onGetTile( - String tileOverlayIdRaw, int x, int y, int zoom) async { - assert(tileOverlayIdRaw != null); - final TileOverlayId tileOverlayId = TileOverlayId(tileOverlayIdRaw); - final TileOverlay tileOverlay = _tileOverlays[tileOverlayId]; - if (tileOverlay == null || tileOverlay.tileProvider == null) { - return TileProvider.noTile; - } - return await tileOverlay.tileProvider.getTile(x, y, zoom); - } } /// Configuration options for the GoogleMaps user interface. From c09b7135d1021118cf908d794c93e4275d05e5a4 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 22 Jan 2021 11:06:03 -0800 Subject: [PATCH 10/18] format --- .../google_maps_flutter/lib/src/controller.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 cd4d5c6df9c7..a68336845ab6 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 @@ -18,7 +18,8 @@ class GoogleMapController { @required this.mapId, }) : assert(_googleMapsFlutterPlatform != null) { _connectStreams(mapId); - _googleMapsFlutterPlatform.updateTileOverlays(mapId: mapId, newTileOverlays: _googleMapState.widget.tileOverlays); + _googleMapsFlutterPlatform.updateTileOverlays( + mapId: mapId, newTileOverlays: _googleMapState.widget.tileOverlays); } /// Initialize control of a [GoogleMap] with [id]. @@ -160,8 +161,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateTileOverlays(Set newTileOverlays) { - return _googleMapsFlutterPlatform.updateTileOverlays(newTileOverlays: newTileOverlays, - mapId: mapId); + return _googleMapsFlutterPlatform.updateTileOverlays( + newTileOverlays: newTileOverlays, mapId: mapId); } /// Clears the tile cache so that all tiles will be requested again from the From b25a00ab8a9cb4932d05e3223ac848bfe50023a0 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 22 Jan 2021 11:15:40 -0800 Subject: [PATCH 11/18] minor refactoring --- .../google_maps_flutter/lib/src/controller.dart | 2 -- .../google_maps_flutter/lib/src/google_map.dart | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) 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 a68336845ab6..3967179b0e50 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 @@ -18,8 +18,6 @@ class GoogleMapController { @required this.mapId, }) : assert(_googleMapsFlutterPlatform != null) { _connectStreams(mapId); - _googleMapsFlutterPlatform.updateTileOverlays( - mapId: mapId, newTileOverlays: _googleMapState.widget.tileOverlays); } /// Initialize control of a [GoogleMap] with [id]. diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index d60b67e5deca..e7f5e32d61b9 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -332,6 +332,7 @@ class _GoogleMapState extends State { this, ); _controller.complete(controller); + _updateTileOverlays(); if (widget.onMapCreated != null) { widget.onMapCreated(controller); } From 8224d5304624ce4c678e43ff94e32bd28a086e02 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 22 Jan 2021 11:34:34 -0800 Subject: [PATCH 12/18] minor java code fixes --- .../flutter/plugins/googlemaps/Convert.java | 25 ++++++++++--------- .../googlemaps/TileProviderController.java | 7 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index dffea23e1245..a0ebec67ddf5 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -79,7 +79,8 @@ private static BitmapDescriptor getBitmapFromBytes(List data) { } } else { throw new IllegalArgumentException( - "fromBytes should have exactly one argument, the bytes. Got: " + data.size()); + "fromBytes should have exactly one argument,interpretTileOverlayOptions the bytes. Got: " + + data.size()); } } @@ -667,22 +668,22 @@ static String interpretTileOverlayOptions(Map data, TileOverlaySink s sink.setFadeIn(toBoolean(fadeIn)); } final Object transparency = data.get("transparency"); - if (transparency != null) { - sink.setTransparency(toFloat(transparency)); + if (transparency instanceof Number) { + sink.setTransparency(((Number) transparency).floatValue()); } final Object zIndex = data.get("zIndex"); - if (zIndex != null) { - sink.setZIndex(toFloat(zIndex)); + if (zIndex instanceof Number) { + sink.setZIndex(((Number) zIndex).intValue()); } final Object visible = data.get("visible"); - if (visible != null) { - sink.setVisible(toBoolean(visible)); + if (visible instanceof Boolean) { + sink.setVisible((Boolean) visible); } - final String tileOverlayId = (String) data.get("tileOverlayId"); - if (tileOverlayId == null) { - throw new IllegalArgumentException("tileOverlayId was null"); + final Object tileOverlayId = data.get("tileOverlayId"); + if (!(tileOverlayId instanceof String)) { + throw new IllegalArgumentException("tileOverlayId was invalid"); } else { - return tileOverlayId; + return (String) tileOverlayId; } } @@ -690,7 +691,7 @@ static Tile interpretTile(Map data) { int width = toInt(data.get("width")); int height = toInt(data.get("height")); byte[] dataArray = null; - if (data.get("data") != null) { + if (data.get("data") instanceof byte[]) { dataArray = (byte[]) data.get("data"); } return new Tile(width, height, dataArray); diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java index a157597af535..f28118c91db4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java @@ -56,8 +56,7 @@ Tile getTile() { Convert.tileOverlayArgumentsToJson(tileOverlayId, x, y, zoom), this)); try { - // `getTile` must be a synchronized operation. - // Because `methodChannel.invokeMethod` is an sync operation, we use a `countDownLatch` to make the process synchronized. + // Because `methodChannel.invokeMethod` is async, we use a `countDownLatch` make it synchronized. countDownLatch.await(); } catch (InterruptedException e) { Log.e( @@ -68,8 +67,8 @@ Tile getTile() { } try { return Convert.interpretTile(result); - } catch (Exception ex) { - Log.e(TAG, "Can't parse tile data", ex); + } catch (Exception e) { + Log.e(TAG, "Can't parse tile data", e); return TileProvider.NO_TILE; } } From b9581f973ba2d1f15bda8d3b102c3f4eabc3d785 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 22 Jan 2021 11:40:04 -0800 Subject: [PATCH 13/18] remove mistake commit --- .../google_maps_flutter/ios/Classes/GoogleMapCircleController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m index cfdf52590527..6688d4d57695 100644 --- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m @@ -35,7 +35,7 @@ - (void)setConsumeTapEvents:(BOOL)consumes { - (void)setVisible:(BOOL)visible { _circle.map = visible ? _mapView : nil; } -- (void)setZIndex:(NSInteger)zIndex { +- (void)setZIndex:(int)zIndex { _circle.zIndex = zIndex; } - (void)setCenter:(CLLocationCoordinate2D)center { From ca69b19af8ed08c661fcf51f9b24b784386d0d46 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 29 Jan 2021 13:54:20 -0800 Subject: [PATCH 14/18] Update packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java Co-authored-by: Maurice Parrish --- .../src/main/java/io/flutter/plugins/googlemaps/Convert.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index a0ebec67ddf5..9ed5baeeafc0 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -79,7 +79,7 @@ private static BitmapDescriptor getBitmapFromBytes(List data) { } } else { throw new IllegalArgumentException( - "fromBytes should have exactly one argument,interpretTileOverlayOptions the bytes. Got: " + "fromBytes should have exactly one argument, interpretTileOverlayOptions the bytes. Got: " + data.size()); } } From 0483931f2648a74dd4b23138465560724175aeab Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 29 Jan 2021 14:12:05 -0800 Subject: [PATCH 15/18] review --- .../flutter/plugins/googlemaps/Convert.java | 22 +++++++++---------- .../FLTGoogleMapTileOverlayController.h | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 9ed5baeeafc0..f9e0ed9c32d0 100644 --- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -668,22 +668,22 @@ static String interpretTileOverlayOptions(Map data, TileOverlaySink s sink.setFadeIn(toBoolean(fadeIn)); } final Object transparency = data.get("transparency"); - if (transparency instanceof Number) { - sink.setTransparency(((Number) transparency).floatValue()); + if (transparency != null) { + sink.setTransparency(toFloat(transparency)); } final Object zIndex = data.get("zIndex"); - if (zIndex instanceof Number) { - sink.setZIndex(((Number) zIndex).intValue()); + if (zIndex != null) { + sink.setZIndex(toFloat(zIndex)); } final Object visible = data.get("visible"); - if (visible instanceof Boolean) { - sink.setVisible((Boolean) visible); + if (visible != null) { + sink.setVisible(toBoolean(visible)); } - final Object tileOverlayId = data.get("tileOverlayId"); - if (!(tileOverlayId instanceof String)) { - throw new IllegalArgumentException("tileOverlayId was invalid"); + final String tileOverlayId = (String) data.get("tileOverlayId"); + if (tileOverlayId == null) { + throw new IllegalArgumentException("tileOverlayId was null"); } else { - return (String) tileOverlayId; + return tileOverlayId; } } @@ -691,7 +691,7 @@ static Tile interpretTile(Map data) { int width = toInt(data.get("width")); int height = toInt(data.get("height")); byte[] dataArray = null; - if (data.get("data") instanceof byte[]) { + if (data.get("data") != null) { dataArray = (byte[]) data.get("data"); } return new Tile(width, height, dataArray); diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h index 90b80bc634f1..f84ad7c20bb4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h +++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h @@ -5,6 +5,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + // Defines map UI options writable from Flutter. @protocol FLTGoogleMapTileOverlayOptionsSink - (void)setFadeIn:(BOOL)fadeIn; @@ -14,8 +16,6 @@ - (void)setTileSize:(NSInteger)tileSize; @end -NS_ASSUME_NONNULL_BEGIN - @interface FLTGoogleMapTileOverlayController : NSObject - (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView; - (void)removeTileOverlay; From 8ceb6a5ddf6d66ce186c9e10a96031cb2cbfb702 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 2 Feb 2021 10:26:58 -0800 Subject: [PATCH 16/18] revert platform_interface to version dependency --- packages/google_maps_flutter/google_maps_flutter/pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 4dcd6807d2cb..ef3a06f5862c 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -7,8 +7,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^1.0.0 - google_maps_flutter_platform_interface: - path: ../google_maps_flutter_platform_interface + google_maps_flutter_platform_interface: ^1.1.0 dev_dependencies: flutter_test: From ac268458a3b2073dc5e0c13c686072b33d199ce9 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 2 Feb 2021 10:34:47 -0800 Subject: [PATCH 17/18] update deps to 1.2.0 --- packages/google_maps_flutter/google_maps_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index ef3a06f5862c..be5c0d449806 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -7,7 +7,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^1.0.0 - google_maps_flutter_platform_interface: ^1.1.0 + google_maps_flutter_platform_interface: ^1.2.0 dev_dependencies: flutter_test: From 3d97fd2feb3e10b5617c943e4c883d46bd5a280d Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 3 Feb 2021 14:35:53 -0800 Subject: [PATCH 18/18] rerun ci