mirror of
https://github.com/flutter/packages.git
synced 2025-06-29 22:33:11 +08:00
[google_maps_flutter_web] Initial support for custom overlays (#3538)
This is a resubmission of https://github.com/flutter/plugins/pull/6982 from the now archived flutter plugins repo. I'm submitting the changes from the original author, @AsturaPhoenix. The original description is below. -------- Saves tile bytes to blobs and uses img elements to decode and render. Does not implement opacity, perform caching, or serve placeholder images. **Issue:** Fixes https://github.com/flutter/flutter/issues/98596 **Known issues:** - https://github.com/flutter/flutter/issues/116132 - https://github.com/AsturaPhoenix/trip_planner_aquamarine/issues/22
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## 0.5.3
|
||||
|
||||
* Initial support for custom overlays. [#98596](https://github.com/flutter/flutter/issues/98596).
|
||||
|
||||
## 0.5.2
|
||||
|
||||
* Adds options for gesture handling and tilt controls.
|
||||
|
@ -14,14 +14,14 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'google_maps_controller_test.mocks.dart';
|
||||
|
||||
@GenerateMocks(<Type>[], customMocks: <MockSpec<dynamic>>[
|
||||
MockSpec<CirclesController>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<PolygonsController>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<PolylinesController>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<MarkersController>(onMissingStub: OnMissingStub.returnDefault),
|
||||
@GenerateNiceMocks(<MockSpec<dynamic>>[
|
||||
MockSpec<CirclesController>(),
|
||||
MockSpec<PolygonsController>(),
|
||||
MockSpec<PolylinesController>(),
|
||||
MockSpec<MarkersController>(),
|
||||
MockSpec<TileOverlaysController>(),
|
||||
])
|
||||
import 'google_maps_controller_test.mocks.dart';
|
||||
|
||||
/// Test Google Map Controller
|
||||
void main() {
|
||||
@ -194,6 +194,15 @@ void main() {
|
||||
}, throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('cannot updateTileOverlays after dispose',
|
||||
(WidgetTester tester) async {
|
||||
controller.dispose();
|
||||
|
||||
expect(() {
|
||||
controller.updateTileOverlays(const <TileOverlay>{});
|
||||
}, throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('isInfoWindowShown defaults to false',
|
||||
(WidgetTester tester) async {
|
||||
controller.dispose();
|
||||
@ -208,6 +217,7 @@ void main() {
|
||||
late MockMarkersController markers;
|
||||
late MockPolygonsController polygons;
|
||||
late MockPolylinesController polylines;
|
||||
late MockTileOverlaysController tileOverlays;
|
||||
late gmaps.GMap map;
|
||||
|
||||
setUp(() {
|
||||
@ -215,20 +225,20 @@ void main() {
|
||||
markers = MockMarkersController();
|
||||
polygons = MockPolygonsController();
|
||||
polylines = MockPolylinesController();
|
||||
tileOverlays = MockTileOverlaysController();
|
||||
map = gmaps.GMap(html.DivElement());
|
||||
});
|
||||
|
||||
testWidgets('listens to map events', (WidgetTester tester) async {
|
||||
controller = createController();
|
||||
controller.debugSetOverrides(
|
||||
createMap: (_, __) => map,
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
);
|
||||
|
||||
controller.init();
|
||||
controller = createController()
|
||||
..debugSetOverrides(
|
||||
createMap: (_, __) => map,
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
)
|
||||
..init();
|
||||
|
||||
// Trigger events on the map, and verify they've been broadcast to the stream
|
||||
final Stream<MapEvent<Object?>> capturedEvents = stream.stream.take(5);
|
||||
@ -258,26 +268,26 @@ void main() {
|
||||
|
||||
testWidgets("binds geometry controllers to map's",
|
||||
(WidgetTester tester) async {
|
||||
controller = createController();
|
||||
controller.debugSetOverrides(
|
||||
createMap: (_, __) => map,
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
);
|
||||
|
||||
controller.init();
|
||||
controller = createController()
|
||||
..debugSetOverrides(
|
||||
createMap: (_, __) => map,
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
tileOverlays: tileOverlays,
|
||||
)
|
||||
..init();
|
||||
|
||||
verify(circles.bindToMap(mapId, map));
|
||||
verify(markers.bindToMap(mapId, map));
|
||||
verify(polygons.bindToMap(mapId, map));
|
||||
verify(polylines.bindToMap(mapId, map));
|
||||
verify(tileOverlays.bindToMap(mapId, map));
|
||||
});
|
||||
|
||||
testWidgets('renders initial geometry', (WidgetTester tester) async {
|
||||
controller = createController(
|
||||
mapObjects: MapObjects(circles: <Circle>{
|
||||
final MapObjects mapObjects = MapObjects(circles: <Circle>{
|
||||
const Circle(
|
||||
circleId: CircleId('circle-1'),
|
||||
zIndex: 1234,
|
||||
@ -320,57 +330,25 @@ void main() {
|
||||
LatLng(43.354469, -5.851318),
|
||||
LatLng(43.354762, -5.850824),
|
||||
])
|
||||
}));
|
||||
}, tileOverlays: <TileOverlay>{
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('overlay-1'))
|
||||
});
|
||||
|
||||
controller.debugSetOverrides(
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
);
|
||||
controller = createController(mapObjects: mapObjects)
|
||||
..debugSetOverrides(
|
||||
circles: circles,
|
||||
markers: markers,
|
||||
polygons: polygons,
|
||||
polylines: polylines,
|
||||
tileOverlays: tileOverlays,
|
||||
)
|
||||
..init();
|
||||
|
||||
controller.init();
|
||||
|
||||
final Set<Circle> capturedCircles =
|
||||
verify(circles.addCircles(captureAny)).captured[0] as Set<Circle>;
|
||||
final Set<Marker> capturedMarkers =
|
||||
verify(markers.addMarkers(captureAny)).captured[0] as Set<Marker>;
|
||||
final Set<Polygon> capturedPolygons =
|
||||
verify(polygons.addPolygons(captureAny)).captured[0]
|
||||
as Set<Polygon>;
|
||||
final Set<Polyline> capturedPolylines =
|
||||
verify(polylines.addPolylines(captureAny)).captured[0]
|
||||
as Set<Polyline>;
|
||||
|
||||
expect(capturedCircles.first.circleId.value, 'circle-1');
|
||||
expect(capturedCircles.first.zIndex, 1234);
|
||||
expect(capturedMarkers.first.markerId.value, 'marker-1');
|
||||
expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test');
|
||||
expect(capturedMarkers.first.infoWindow.title, 'title for test');
|
||||
expect(capturedPolygons.first.polygonId.value, 'polygon-1');
|
||||
expect(capturedPolygons.elementAt(1).polygonId.value,
|
||||
'polygon-2-with-holes');
|
||||
expect(capturedPolygons.elementAt(1).holes, isNot(null));
|
||||
expect(capturedPolylines.first.polylineId.value, 'polyline-1');
|
||||
});
|
||||
|
||||
testWidgets('empty infoWindow does not create InfoWindow instance.',
|
||||
(WidgetTester tester) async {
|
||||
controller = createController(
|
||||
mapObjects: MapObjects(markers: <Marker>{
|
||||
const Marker(markerId: MarkerId('marker-1')),
|
||||
}));
|
||||
|
||||
controller.debugSetOverrides(
|
||||
markers: markers,
|
||||
);
|
||||
|
||||
controller.init();
|
||||
|
||||
final Set<Marker> capturedMarkers =
|
||||
verify(markers.addMarkers(captureAny)).captured[0] as Set<Marker>;
|
||||
|
||||
expect(capturedMarkers.first.infoWindow, InfoWindow.noText);
|
||||
verify(circles.addCircles(mapObjects.circles));
|
||||
verify(markers.addMarkers(mapObjects.markers));
|
||||
verify(polygons.addPolygons(mapObjects.polygons));
|
||||
verify(polylines.addPolylines(mapObjects.polylines));
|
||||
verify(tileOverlays.addTileOverlays(mapObjects.tileOverlays));
|
||||
});
|
||||
|
||||
group('Initialization options', () {
|
||||
@ -449,15 +427,12 @@ void main() {
|
||||
target: LatLng(43.308, -5.6910),
|
||||
zoom: 12,
|
||||
),
|
||||
);
|
||||
|
||||
controller.debugSetOverrides(
|
||||
createMap: (_, gmaps.MapOptions options) {
|
||||
capturedOptions = options;
|
||||
return map;
|
||||
});
|
||||
|
||||
controller.init();
|
||||
)
|
||||
..debugSetOverrides(createMap: (_, gmaps.MapOptions options) {
|
||||
capturedOptions = options;
|
||||
return map;
|
||||
})
|
||||
..init();
|
||||
|
||||
expect(capturedOptions, isNotNull);
|
||||
expect(capturedOptions!.zoom, 12);
|
||||
@ -467,8 +442,7 @@ void main() {
|
||||
|
||||
group('Traffic Layer', () {
|
||||
testWidgets('by default is disabled', (WidgetTester tester) async {
|
||||
controller = createController();
|
||||
controller.init();
|
||||
controller = createController()..init();
|
||||
expect(controller.trafficLayer, isNull);
|
||||
});
|
||||
|
||||
@ -477,9 +451,9 @@ void main() {
|
||||
controller = createController(
|
||||
mapConfiguration: const MapConfiguration(
|
||||
trafficEnabled: true,
|
||||
));
|
||||
controller.debugSetOverrides(createMap: (_, __) => map);
|
||||
controller.init();
|
||||
))
|
||||
..debugSetOverrides(createMap: (_, __) => map)
|
||||
..init();
|
||||
expect(controller.trafficLayer, isNotNull);
|
||||
});
|
||||
});
|
||||
@ -496,9 +470,9 @@ void main() {
|
||||
..zoom = 10
|
||||
..center = gmaps.LatLng(0, 0),
|
||||
);
|
||||
controller = createController();
|
||||
controller.debugSetOverrides(createMap: (_, __) => map);
|
||||
controller.init();
|
||||
controller = createController()
|
||||
..debugSetOverrides(createMap: (_, __) => map)
|
||||
..init();
|
||||
});
|
||||
|
||||
group('updateRawOptions', () {
|
||||
@ -556,13 +530,9 @@ void main() {
|
||||
|
||||
// These are the methods that get forwarded to other controllers, so we just verify calls.
|
||||
group('Pass-through methods', () {
|
||||
setUp(() {
|
||||
controller = createController();
|
||||
});
|
||||
|
||||
testWidgets('updateCircles', (WidgetTester tester) async {
|
||||
final MockCirclesController mock = MockCirclesController();
|
||||
controller.debugSetOverrides(circles: mock);
|
||||
controller = createController()..debugSetOverrides(circles: mock);
|
||||
|
||||
final Set<Circle> previous = <Circle>{
|
||||
const Circle(circleId: CircleId('to-be-updated')),
|
||||
@ -589,7 +559,7 @@ void main() {
|
||||
|
||||
testWidgets('updateMarkers', (WidgetTester tester) async {
|
||||
final MockMarkersController mock = MockMarkersController();
|
||||
controller.debugSetOverrides(markers: mock);
|
||||
controller = createController()..debugSetOverrides(markers: mock);
|
||||
|
||||
final Set<Marker> previous = <Marker>{
|
||||
const Marker(markerId: MarkerId('to-be-updated')),
|
||||
@ -616,7 +586,7 @@ void main() {
|
||||
|
||||
testWidgets('updatePolygons', (WidgetTester tester) async {
|
||||
final MockPolygonsController mock = MockPolygonsController();
|
||||
controller.debugSetOverrides(polygons: mock);
|
||||
controller = createController()..debugSetOverrides(polygons: mock);
|
||||
|
||||
final Set<Polygon> previous = <Polygon>{
|
||||
const Polygon(polygonId: PolygonId('to-be-updated')),
|
||||
@ -643,7 +613,7 @@ void main() {
|
||||
|
||||
testWidgets('updatePolylines', (WidgetTester tester) async {
|
||||
final MockPolylinesController mock = MockPolylinesController();
|
||||
controller.debugSetOverrides(polylines: mock);
|
||||
controller = createController()..debugSetOverrides(polylines: mock);
|
||||
|
||||
final Set<Polyline> previous = <Polyline>{
|
||||
const Polyline(polylineId: PolylineId('to-be-updated')),
|
||||
@ -674,11 +644,38 @@ void main() {
|
||||
}));
|
||||
});
|
||||
|
||||
testWidgets('updateTileOverlays', (WidgetTester tester) async {
|
||||
final MockTileOverlaysController mock = MockTileOverlaysController();
|
||||
controller = createController(
|
||||
mapObjects: MapObjects(tileOverlays: <TileOverlay>{
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('to-be-updated')),
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('to-be-removed')),
|
||||
}))
|
||||
..debugSetOverrides(tileOverlays: mock);
|
||||
|
||||
controller.updateTileOverlays(<TileOverlay>{
|
||||
const TileOverlay(
|
||||
tileOverlayId: TileOverlayId('to-be-updated'), visible: false),
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('to-be-added')),
|
||||
});
|
||||
|
||||
verify(mock.removeTileOverlays(<TileOverlayId>{
|
||||
const TileOverlayId('to-be-removed'),
|
||||
}));
|
||||
verify(mock.addTileOverlays(<TileOverlay>{
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('to-be-added')),
|
||||
}));
|
||||
verify(mock.changeTileOverlays(<TileOverlay>{
|
||||
const TileOverlay(
|
||||
tileOverlayId: TileOverlayId('to-be-updated'), visible: false),
|
||||
}));
|
||||
});
|
||||
|
||||
testWidgets('infoWindow visibility', (WidgetTester tester) async {
|
||||
final MockMarkersController mock = MockMarkersController();
|
||||
const MarkerId markerId = MarkerId('marker-with-infowindow');
|
||||
when(mock.isInfoWindowShown(markerId)).thenReturn(true);
|
||||
controller.debugSetOverrides(markers: mock);
|
||||
controller = createController()..debugSetOverrides(markers: mock);
|
||||
|
||||
controller.showInfoWindow(markerId);
|
||||
|
||||
|
@ -403,3 +403,65 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController {
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [TileOverlaysController].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTileOverlaysController extends _i1.Mock
|
||||
implements _i3.TileOverlaysController {
|
||||
@override
|
||||
_i2.GMap get googleMap => (super.noSuchMethod(
|
||||
Invocation.getter(#googleMap),
|
||||
returnValue: _FakeGMap_0(
|
||||
this,
|
||||
Invocation.getter(#googleMap),
|
||||
),
|
||||
returnValueForMissingStub: _FakeGMap_0(
|
||||
this,
|
||||
Invocation.getter(#googleMap),
|
||||
),
|
||||
) as _i2.GMap);
|
||||
@override
|
||||
set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#googleMap,
|
||||
_googleMap,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void addTileOverlays(Set<_i4.TileOverlay>? tileOverlays) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addTileOverlays,
|
||||
[tileOverlays],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void changeTileOverlays(Set<_i4.TileOverlay>? tileOverlays) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#changeTileOverlays,
|
||||
[tileOverlays],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeTileOverlays(Set<_i4.TileOverlayId>? tileOverlayIds) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeTileOverlays,
|
||||
[tileOverlayIds],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void clearTileCache(_i4.TileOverlayId? tileOverlayId) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#clearTileCache,
|
||||
[tileOverlayId],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
}
|
||||
|
@ -14,12 +14,9 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
@GenerateNiceMocks(<MockSpec<dynamic>>[MockSpec<GoogleMapController>()])
|
||||
import 'google_maps_plugin_test.mocks.dart';
|
||||
|
||||
@GenerateMocks(<Type>[], customMocks: <MockSpec<dynamic>>[
|
||||
MockSpec<GoogleMapController>(onMissingStub: OnMissingStub.returnDefault),
|
||||
])
|
||||
|
||||
/// Test GoogleMapsPlugin
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -208,28 +205,6 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('Noop methods:', () {
|
||||
const int mapId = 0;
|
||||
setUp(() {
|
||||
plugin.debugSetMapById(<int, GoogleMapController>{mapId: controller});
|
||||
});
|
||||
// Options
|
||||
testWidgets('updateTileOverlays', (WidgetTester tester) async {
|
||||
final Future<void> update = plugin.updateTileOverlays(
|
||||
mapId: mapId,
|
||||
newTileOverlays: <TileOverlay>{},
|
||||
);
|
||||
expect(update, completion(null));
|
||||
});
|
||||
testWidgets('updateTileOverlays', (WidgetTester tester) async {
|
||||
final Future<void> update = plugin.clearTileCache(
|
||||
const TileOverlayId('any'),
|
||||
mapId: mapId,
|
||||
);
|
||||
expect(update, completion(null));
|
||||
});
|
||||
});
|
||||
|
||||
// These methods only pass-through values from the plugin to the controller
|
||||
// so we verify them all together here...
|
||||
group('Pass-through methods:', () {
|
||||
@ -287,6 +262,24 @@ void main() {
|
||||
|
||||
verify(controller.updateCircles(expectedUpdates));
|
||||
});
|
||||
// Tile Overlays
|
||||
testWidgets('updateTileOverlays', (WidgetTester tester) async {
|
||||
final Set<TileOverlay> expectedOverlays = <TileOverlay>{
|
||||
const TileOverlay(tileOverlayId: TileOverlayId('overlay'))
|
||||
};
|
||||
|
||||
await plugin.updateTileOverlays(
|
||||
newTileOverlays: expectedOverlays, mapId: mapId);
|
||||
|
||||
verify(controller.updateTileOverlays(expectedOverlays));
|
||||
});
|
||||
testWidgets('clearTileCache', (WidgetTester tester) async {
|
||||
const TileOverlayId tileOverlayId = TileOverlayId('Dory');
|
||||
|
||||
await plugin.clearTileCache(tileOverlayId, mapId: mapId);
|
||||
|
||||
verify(controller.clearTileCache(tileOverlayId));
|
||||
});
|
||||
// Camera
|
||||
testWidgets('animateCamera', (WidgetTester tester) async {
|
||||
final CameraUpdate expectedUpdates = CameraUpdate.newLatLng(
|
||||
|
@ -126,6 +126,7 @@ class MockGoogleMapController extends _i1.Mock
|
||||
_i4.CirclesController? circles,
|
||||
_i4.PolygonsController? polygons,
|
||||
_i4.PolylinesController? polylines,
|
||||
_i4.TileOverlaysController? tileOverlays,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
@ -137,6 +138,7 @@ class MockGoogleMapController extends _i1.Mock
|
||||
#circles: circles,
|
||||
#polygons: polygons,
|
||||
#polylines: polylines,
|
||||
#tileOverlays: tileOverlays,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
@ -286,6 +288,23 @@ class MockGoogleMapController extends _i1.Mock
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void updateTileOverlays(Set<_i2.TileOverlay>? newOverlays) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#updateTileOverlays,
|
||||
[newOverlays],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void clearTileCache(_i2.TileOverlayId? id) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#clearTileCache,
|
||||
[id],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void showInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#showInfoWindow,
|
||||
|
@ -0,0 +1,119 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:google_maps/google_maps.dart' as gmaps;
|
||||
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
|
||||
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'resources/tile16_base64.dart';
|
||||
|
||||
class NoTileProvider implements TileProvider {
|
||||
const NoTileProvider();
|
||||
|
||||
@override
|
||||
Future<Tile> getTile(int x, int y, int? zoom) async => TileProvider.noTile;
|
||||
}
|
||||
|
||||
class TestTileProvider implements TileProvider {
|
||||
const TestTileProvider();
|
||||
|
||||
@override
|
||||
Future<Tile> getTile(int x, int y, int? zoom) async =>
|
||||
Tile(16, 16, const Base64Decoder().convert(tile16Base64));
|
||||
}
|
||||
|
||||
/// Test Overlays
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('TileOverlayController', () {
|
||||
const TileOverlayId id = TileOverlayId('');
|
||||
|
||||
testWidgets('minimal initialization', (WidgetTester tester) async {
|
||||
final TileOverlayController controller = TileOverlayController(
|
||||
tileOverlay: const TileOverlay(tileOverlayId: id),
|
||||
);
|
||||
|
||||
final gmaps.Size size = controller.gmMapType.tileSize!;
|
||||
expect(size.width, TileOverlayController.logicalTileSize);
|
||||
expect(size.height, TileOverlayController.logicalTileSize);
|
||||
expect(controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document),
|
||||
null);
|
||||
});
|
||||
|
||||
testWidgets('produces image tiles', (WidgetTester tester) async {
|
||||
final TileOverlayController controller = TileOverlayController(
|
||||
tileOverlay: const TileOverlay(
|
||||
tileOverlayId: id,
|
||||
tileProvider: TestTileProvider(),
|
||||
),
|
||||
);
|
||||
|
||||
final html.ImageElement img =
|
||||
controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
|
||||
as html.ImageElement;
|
||||
expect(img.naturalWidth, 0);
|
||||
expect(img.naturalHeight, 0);
|
||||
expect(img.hidden, true);
|
||||
|
||||
// Wait until the image is fully loaded and decoded before re-reading its attributes.
|
||||
await img.onLoad.first;
|
||||
await img.decode();
|
||||
|
||||
expect(img.hidden, false);
|
||||
expect(img.naturalWidth, 16);
|
||||
expect(img.naturalHeight, 16);
|
||||
});
|
||||
|
||||
testWidgets('update', (WidgetTester tester) async {
|
||||
final TileOverlayController controller = TileOverlayController(
|
||||
tileOverlay: const TileOverlay(
|
||||
tileOverlayId: id,
|
||||
tileProvider: NoTileProvider(),
|
||||
),
|
||||
);
|
||||
{
|
||||
final html.ImageElement img =
|
||||
controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
|
||||
as html.ImageElement;
|
||||
await null; // let `getTile` `then` complete
|
||||
expect(
|
||||
img.src,
|
||||
isEmpty,
|
||||
reason: 'The NoTileProvider never updates the img src',
|
||||
);
|
||||
}
|
||||
|
||||
controller.update(const TileOverlay(
|
||||
tileOverlayId: id,
|
||||
tileProvider: TestTileProvider(),
|
||||
));
|
||||
{
|
||||
final html.ImageElement img =
|
||||
controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
|
||||
as html.ImageElement;
|
||||
await img.onLoad.first;
|
||||
expect(
|
||||
img.src,
|
||||
isNotEmpty,
|
||||
reason: 'The img `src` should eventually become the Blob URL.',
|
||||
);
|
||||
}
|
||||
|
||||
controller.update(const TileOverlay(tileOverlayId: id));
|
||||
{
|
||||
expect(
|
||||
controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document),
|
||||
null,
|
||||
reason: 'Setting a null tileProvider should work.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:google_maps/google_maps.dart' as gmaps;
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'
|
||||
hide GoogleMapController;
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
@GenerateNiceMocks(<MockSpec<dynamic>>[MockSpec<TileProvider>()])
|
||||
import 'overlays_test.mocks.dart';
|
||||
|
||||
MockTileProvider neverTileProvider() {
|
||||
final MockTileProvider tileProvider = MockTileProvider();
|
||||
when(tileProvider.getTile(any, any, any))
|
||||
.thenAnswer((_) => Completer<Tile>().future);
|
||||
return tileProvider;
|
||||
}
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('TileOverlaysController', () {
|
||||
late TileOverlaysController controller;
|
||||
late gmaps.GMap map;
|
||||
late List<MockTileProvider> tileProviders;
|
||||
late List<TileOverlay> tileOverlays;
|
||||
|
||||
/// Queries the current overlay map types for tiles at x = 0, y = 0, zoom =
|
||||
/// 0.
|
||||
void probeTiles() {
|
||||
for (final gmaps.MapType? mapType in map.overlayMapTypes!.array!) {
|
||||
mapType?.getTile!(gmaps.Point(0, 0), 0, html.document);
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
controller = TileOverlaysController();
|
||||
map = gmaps.GMap(html.DivElement());
|
||||
controller.googleMap = map;
|
||||
|
||||
tileProviders = <MockTileProvider>[
|
||||
for (int i = 0; i < 3; ++i) neverTileProvider()
|
||||
];
|
||||
|
||||
tileOverlays = <TileOverlay>[
|
||||
for (int i = 0; i < 3; ++i)
|
||||
TileOverlay(
|
||||
tileOverlayId: TileOverlayId('$i'),
|
||||
tileProvider: tileProviders[i],
|
||||
zIndex: i)
|
||||
];
|
||||
});
|
||||
|
||||
testWidgets('addTileOverlays', (WidgetTester tester) async {
|
||||
controller.addTileOverlays(<TileOverlay>{...tileOverlays});
|
||||
probeTiles();
|
||||
verifyInOrder(<dynamic>[
|
||||
tileProviders[0].getTile(any, any, any),
|
||||
tileProviders[1].getTile(any, any, any),
|
||||
tileProviders[2].getTile(any, any, any),
|
||||
]);
|
||||
verifyNoMoreInteractions(tileProviders[0]);
|
||||
verifyNoMoreInteractions(tileProviders[1]);
|
||||
verifyNoMoreInteractions(tileProviders[2]);
|
||||
});
|
||||
|
||||
testWidgets('changeTileOverlays', (WidgetTester tester) async {
|
||||
controller.addTileOverlays(<TileOverlay>{...tileOverlays});
|
||||
|
||||
// Set overlay 0 visiblity to false; flip z ordering of 1 and 2, leaving 1
|
||||
// unchanged.
|
||||
controller.changeTileOverlays(<TileOverlay>{
|
||||
tileOverlays[0].copyWith(visibleParam: false),
|
||||
tileOverlays[2].copyWith(zIndexParam: 0),
|
||||
});
|
||||
|
||||
probeTiles();
|
||||
|
||||
verifyInOrder(<dynamic>[
|
||||
tileProviders[2].getTile(any, any, any),
|
||||
tileProviders[1].getTile(any, any, any),
|
||||
]);
|
||||
verifyZeroInteractions(tileProviders[0]);
|
||||
verifyNoMoreInteractions(tileProviders[1]);
|
||||
verifyNoMoreInteractions(tileProviders[2]);
|
||||
|
||||
// Re-enable overlay 0.
|
||||
controller.changeTileOverlays(
|
||||
<TileOverlay>{tileOverlays[0].copyWith(visibleParam: true)});
|
||||
|
||||
probeTiles();
|
||||
|
||||
verify(tileProviders[2].getTile(any, any, any));
|
||||
verifyInOrder(<dynamic>[
|
||||
tileProviders[0].getTile(any, any, any),
|
||||
tileProviders[1].getTile(any, any, any),
|
||||
]);
|
||||
verifyNoMoreInteractions(tileProviders[0]);
|
||||
verifyNoMoreInteractions(tileProviders[1]);
|
||||
verifyNoMoreInteractions(tileProviders[2]);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'updating the z index of a hidden layer does not make it visible',
|
||||
(WidgetTester tester) async {
|
||||
controller.addTileOverlays(<TileOverlay>{...tileOverlays});
|
||||
|
||||
controller.changeTileOverlays(<TileOverlay>{
|
||||
tileOverlays[0].copyWith(zIndexParam: -1, visibleParam: false),
|
||||
});
|
||||
|
||||
probeTiles();
|
||||
verifyZeroInteractions(tileProviders[0]);
|
||||
});
|
||||
|
||||
testWidgets('removeTileOverlays', (WidgetTester tester) async {
|
||||
controller.addTileOverlays(<TileOverlay>{...tileOverlays});
|
||||
|
||||
controller.removeTileOverlays(<TileOverlayId>{
|
||||
tileOverlays[0].tileOverlayId,
|
||||
tileOverlays[2].tileOverlayId,
|
||||
});
|
||||
|
||||
probeTiles();
|
||||
|
||||
verify(tileProviders[1].getTile(any, any, any));
|
||||
verifyZeroInteractions(tileProviders[0]);
|
||||
verifyZeroInteractions(tileProviders[2]);
|
||||
});
|
||||
|
||||
testWidgets('clearTileCache', (WidgetTester tester) async {
|
||||
final Completer<GoogleMapController> controllerCompleter =
|
||||
Completer<GoogleMapController>();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
body: GoogleMap(
|
||||
initialCameraPosition: const CameraPosition(
|
||||
target: LatLng(43.3078, -5.6958),
|
||||
zoom: 14,
|
||||
),
|
||||
tileOverlays: <TileOverlay>{...tileOverlays.take(2)},
|
||||
onMapCreated: (GoogleMapController value) {
|
||||
controllerCompleter.complete(value);
|
||||
addTearDown(() => value.dispose());
|
||||
},
|
||||
))));
|
||||
|
||||
// This is needed to kick-off the rendering of the JS Map flutter widget
|
||||
await tester.pump();
|
||||
final GoogleMapController controller = await controllerCompleter.future;
|
||||
|
||||
await tester.pump();
|
||||
verify(tileProviders[0].getTile(any, any, any));
|
||||
verify(tileProviders[1].getTile(any, any, any));
|
||||
|
||||
await controller.clearTileCache(tileOverlays[0].tileOverlayId);
|
||||
|
||||
await tester.pump();
|
||||
verify(tileProviders[0].getTile(any, any, any));
|
||||
verifyNoMoreInteractions(tileProviders[1]);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Mocks generated by Mockito 5.4.1 from annotations
|
||||
// in google_maps_flutter_web_integration_tests/integration_test/overlays_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// @dart=2.19
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
|
||||
import 'package:google_maps_flutter_platform_interface/src/types/types.dart'
|
||||
as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
class _FakeTile_0 extends _i1.SmartFake implements _i2.Tile {
|
||||
_FakeTile_0(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [TileProvider].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTileProvider extends _i1.Mock implements _i2.TileProvider {
|
||||
@override
|
||||
_i3.Future<_i2.Tile> getTile(
|
||||
int? x,
|
||||
int? y,
|
||||
int? zoom,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getTile,
|
||||
[
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
],
|
||||
),
|
||||
returnValue: _i3.Future<_i2.Tile>.value(_FakeTile_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getTile,
|
||||
[
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
],
|
||||
),
|
||||
)),
|
||||
returnValueForMissingStub: _i3.Future<_i2.Tile>.value(_FakeTile_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getTile,
|
||||
[
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
],
|
||||
),
|
||||
)),
|
||||
) as _i3.Future<_i2.Tile>);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// 16x16 transparent png.
|
||||
const String tile16Base64 =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAA'
|
||||
'Cxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAATSURBVDhPYxgFo2AUjAIwYGAAAAQQAAGn'
|
||||
'RHxjAAAAAElFTkSuQmCC';
|
@ -9,6 +9,7 @@ import 'dart:convert';
|
||||
import 'dart:html';
|
||||
import 'dart:js_util';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -31,6 +32,8 @@ part 'src/google_maps_controller.dart';
|
||||
part 'src/google_maps_flutter_web.dart';
|
||||
part 'src/marker.dart';
|
||||
part 'src/markers.dart';
|
||||
part 'src/overlay.dart';
|
||||
part 'src/overlays.dart';
|
||||
part 'src/polygon.dart';
|
||||
part 'src/polygons.dart';
|
||||
part 'src/polyline.dart';
|
||||
|
@ -25,11 +25,13 @@ class GoogleMapController {
|
||||
_polygons = mapObjects.polygons,
|
||||
_polylines = mapObjects.polylines,
|
||||
_circles = mapObjects.circles,
|
||||
_tileOverlays = mapObjects.tileOverlays,
|
||||
_lastMapConfiguration = mapConfiguration {
|
||||
_circlesController = CirclesController(stream: _streamController);
|
||||
_polygonsController = PolygonsController(stream: _streamController);
|
||||
_polylinesController = PolylinesController(stream: _streamController);
|
||||
_markersController = MarkersController(stream: _streamController);
|
||||
_tileOverlaysController = TileOverlaysController();
|
||||
|
||||
// Register the view factory that will hold the `_div` that holds the map in the DOM.
|
||||
// The `_div` needs to be created outside of the ViewFactory (and cached!) so we can
|
||||
@ -53,6 +55,7 @@ class GoogleMapController {
|
||||
final Set<Polygon> _polygons;
|
||||
final Set<Polyline> _polylines;
|
||||
final Set<Circle> _circles;
|
||||
Set<TileOverlay> _tileOverlays;
|
||||
// The configuration passed by the user, before converting to gmaps.
|
||||
// Caching this allows us to re-create the map faithfully when needed.
|
||||
MapConfiguration _lastMapConfiguration = const MapConfiguration();
|
||||
@ -108,6 +111,7 @@ class GoogleMapController {
|
||||
PolygonsController? _polygonsController;
|
||||
PolylinesController? _polylinesController;
|
||||
MarkersController? _markersController;
|
||||
TileOverlaysController? _tileOverlaysController;
|
||||
// Keeps track if _attachGeometryControllers has been called or not.
|
||||
bool _controllersBoundToMap = false;
|
||||
|
||||
@ -122,12 +126,14 @@ class GoogleMapController {
|
||||
CirclesController? circles,
|
||||
PolygonsController? polygons,
|
||||
PolylinesController? polylines,
|
||||
TileOverlaysController? tileOverlays,
|
||||
}) {
|
||||
_overrideCreateMap = createMap;
|
||||
_markersController = markers ?? _markersController;
|
||||
_circlesController = circles ?? _circlesController;
|
||||
_polygonsController = polygons ?? _polygonsController;
|
||||
_polylinesController = polylines ?? _polylinesController;
|
||||
_tileOverlaysController = tileOverlays ?? _tileOverlaysController;
|
||||
}
|
||||
|
||||
DebugCreateMapFunction? _overrideCreateMap;
|
||||
@ -182,13 +188,7 @@ class GoogleMapController {
|
||||
_attachGeometryControllers(map);
|
||||
|
||||
// Now attach the geometry, traffic and any other layers...
|
||||
_renderInitialGeometry(
|
||||
markers: _markers,
|
||||
circles: _circles,
|
||||
polygons: _polygons,
|
||||
polylines: _polylines,
|
||||
);
|
||||
|
||||
_renderInitialGeometry();
|
||||
_setTrafficLayer(map, _lastMapConfiguration.trafficEnabled ?? false);
|
||||
}
|
||||
|
||||
@ -241,22 +241,20 @@ class GoogleMapController {
|
||||
'Cannot attach a map to a null PolylinesController instance.');
|
||||
assert(_markersController != null,
|
||||
'Cannot attach a map to a null MarkersController instance.');
|
||||
assert(_tileOverlaysController != null,
|
||||
'Cannot attach a map to a null TileOverlaysController instance.');
|
||||
|
||||
_circlesController!.bindToMap(_mapId, map);
|
||||
_polygonsController!.bindToMap(_mapId, map);
|
||||
_polylinesController!.bindToMap(_mapId, map);
|
||||
_markersController!.bindToMap(_mapId, map);
|
||||
_tileOverlaysController!.bindToMap(_mapId, map);
|
||||
|
||||
_controllersBoundToMap = true;
|
||||
}
|
||||
|
||||
// Renders the initial sets of geometry.
|
||||
void _renderInitialGeometry({
|
||||
Set<Marker> markers = const <Marker>{},
|
||||
Set<Circle> circles = const <Circle>{},
|
||||
Set<Polygon> polygons = const <Polygon>{},
|
||||
Set<Polyline> polylines = const <Polyline>{},
|
||||
}) {
|
||||
void _renderInitialGeometry() {
|
||||
assert(
|
||||
_controllersBoundToMap,
|
||||
'Geometry controllers must be bound to a map before any geometry can '
|
||||
@ -266,10 +264,11 @@ class GoogleMapController {
|
||||
// in the [_attachGeometryControllers] method, which ensures that all these
|
||||
// controllers below are *not* null.
|
||||
|
||||
_markersController!.addMarkers(markers);
|
||||
_circlesController!.addCircles(circles);
|
||||
_polygonsController!.addPolygons(polygons);
|
||||
_polylinesController!.addPolylines(polylines);
|
||||
_markersController!.addMarkers(_markers);
|
||||
_circlesController!.addCircles(_circles);
|
||||
_polygonsController!.addPolygons(_polygons);
|
||||
_polylinesController!.addPolylines(_polylines);
|
||||
_tileOverlaysController!.addTileOverlays(_tileOverlays);
|
||||
}
|
||||
|
||||
// Merges new options coming from the plugin into _lastConfiguration.
|
||||
@ -407,6 +406,25 @@ class GoogleMapController {
|
||||
_markersController?.removeMarkers(updates.markerIdsToRemove);
|
||||
}
|
||||
|
||||
/// Updates the set of [TileOverlay]s.
|
||||
void updateTileOverlays(Set<TileOverlay> newOverlays) {
|
||||
final MapsObjectUpdates<TileOverlay> updates =
|
||||
MapsObjectUpdates<TileOverlay>.from(_tileOverlays, newOverlays,
|
||||
objectName: 'tileOverlay');
|
||||
assert(_tileOverlaysController != null,
|
||||
'Cannot update tile overlays after dispose().');
|
||||
_tileOverlaysController?.addTileOverlays(updates.objectsToAdd);
|
||||
_tileOverlaysController?.changeTileOverlays(updates.objectsToChange);
|
||||
_tileOverlaysController
|
||||
?.removeTileOverlays(updates.objectIdsToRemove.cast<TileOverlayId>());
|
||||
_tileOverlays = newOverlays;
|
||||
}
|
||||
|
||||
/// Clears the tile cache associated with the given [TileOverlayId].
|
||||
void clearTileCache(TileOverlayId id) {
|
||||
_tileOverlaysController?.clearTileCache(id);
|
||||
}
|
||||
|
||||
/// Shows the [InfoWindow] of the marker identified by its [MarkerId].
|
||||
void showInfoWindow(MarkerId markerId) {
|
||||
assert(_markersController != null,
|
||||
@ -439,6 +457,7 @@ class GoogleMapController {
|
||||
_polygonsController = null;
|
||||
_polylinesController = null;
|
||||
_markersController = null;
|
||||
_tileOverlaysController = null;
|
||||
_streamController.close();
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform {
|
||||
required Set<TileOverlay> newTileOverlays,
|
||||
required int mapId,
|
||||
}) async {
|
||||
return; // Noop for now!
|
||||
_map(mapId).updateTileOverlays(newTileOverlays);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -103,7 +103,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform {
|
||||
TileOverlayId tileOverlayId, {
|
||||
required int mapId,
|
||||
}) async {
|
||||
return; // Noop for now!
|
||||
_map(mapId).clearTileCache(tileOverlayId);
|
||||
}
|
||||
|
||||
/// Applies the given `cameraUpdate` to the current viewport (with animation).
|
||||
|
@ -0,0 +1,76 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
part of google_maps_flutter_web;
|
||||
|
||||
/// This wraps a [TileOverlay] in a [gmaps.MapType].
|
||||
class TileOverlayController {
|
||||
/// Creates a `TileOverlayController` that wraps a [TileOverlay] object and its corresponding [gmaps.MapType].
|
||||
TileOverlayController({
|
||||
required TileOverlay tileOverlay,
|
||||
}) {
|
||||
update(tileOverlay);
|
||||
}
|
||||
|
||||
/// The size in pixels of the (square) tiles passed to the Maps SDK.
|
||||
///
|
||||
/// Even though the web supports any size, and rectangular tiles, for
|
||||
/// for consistency with mobile, this is not configurable on the web.
|
||||
/// (Both Android and iOS prefer square 256px tiles @ 1x DPI)
|
||||
///
|
||||
/// For higher DPI screens, the Tile that is actually returned can be larger
|
||||
/// than 256px square.
|
||||
static const int logicalTileSize = 256;
|
||||
|
||||
/// Updates the [gmaps.MapType] and cached properties with an updated
|
||||
/// [TileOverlay].
|
||||
void update(TileOverlay tileOverlay) {
|
||||
_tileOverlay = tileOverlay;
|
||||
_gmMapType = gmaps.MapType()
|
||||
..tileSize = gmaps.Size(logicalTileSize, logicalTileSize)
|
||||
..getTile = _getTile;
|
||||
}
|
||||
|
||||
/// Renders a Tile for gmaps; delegating to the configured [TileProvider].
|
||||
HtmlElement? _getTile(
|
||||
gmaps.Point? tileCoord,
|
||||
num? zoom,
|
||||
Document? ownerDocument,
|
||||
) {
|
||||
if (_tileOverlay.tileProvider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ImageElement img =
|
||||
ownerDocument!.createElement('img') as ImageElement;
|
||||
img.width = img.height = logicalTileSize;
|
||||
img.hidden = true;
|
||||
img.setAttribute('decoding', 'async');
|
||||
|
||||
_tileOverlay.tileProvider!
|
||||
.getTile(tileCoord!.x!.toInt(), tileCoord.y!.toInt(), zoom?.toInt())
|
||||
.then((Tile tile) {
|
||||
if (tile.data == null) {
|
||||
return;
|
||||
}
|
||||
// Using img lets us take advantage of native decoding.
|
||||
final String src = Url.createObjectUrl(Blob(<Object?>[tile.data]));
|
||||
img.src = src;
|
||||
img.addEventListener('load', (_) {
|
||||
img.hidden = false;
|
||||
Url.revokeObjectUrl(src);
|
||||
});
|
||||
});
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/// The [gmaps.MapType] produced by this controller.
|
||||
gmaps.MapType get gmMapType => _gmMapType;
|
||||
late gmaps.MapType _gmMapType;
|
||||
|
||||
/// The [TileOverlay] providing data for this controller.
|
||||
TileOverlay get tileOverlay => _tileOverlay;
|
||||
late TileOverlay _tileOverlay;
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
part of google_maps_flutter_web;
|
||||
|
||||
/// This class manages all the [TileOverlayController]s associated to a [GoogleMapController].
|
||||
class TileOverlaysController extends GeometryController {
|
||||
final Map<TileOverlayId, TileOverlayController> _tileOverlays =
|
||||
<TileOverlayId, TileOverlayController>{};
|
||||
final List<TileOverlayController> _visibleTileOverlays =
|
||||
<TileOverlayController>[];
|
||||
|
||||
// Inserts `tileOverlayController` into the list of visible overlays, and the current [googleMap].
|
||||
//
|
||||
// After insertion, the arrays stay sorted by ascending z-index.
|
||||
void _insertZSorted(TileOverlayController tileOverlayController) {
|
||||
final int index = _visibleTileOverlays.lowerBoundBy<num>(
|
||||
tileOverlayController,
|
||||
(TileOverlayController c) => c.tileOverlay.zIndex);
|
||||
|
||||
googleMap.overlayMapTypes!.insertAt(index, tileOverlayController.gmMapType);
|
||||
_visibleTileOverlays.insert(index, tileOverlayController);
|
||||
}
|
||||
|
||||
// Removes `tileOverlayController` from the list of visible overlays.
|
||||
void _remove(TileOverlayController tileOverlayController) {
|
||||
final int index = _visibleTileOverlays.indexOf(tileOverlayController);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
googleMap.overlayMapTypes!.removeAt(index);
|
||||
_visibleTileOverlays.removeAt(index);
|
||||
}
|
||||
|
||||
/// Adds new [TileOverlay]s to this controller.
|
||||
///
|
||||
/// Wraps the [TileOverlay]s in corresponding [TileOverlayController]s.
|
||||
void addTileOverlays(Set<TileOverlay> tileOverlaysToAdd) {
|
||||
tileOverlaysToAdd.forEach(_addTileOverlay);
|
||||
}
|
||||
|
||||
void _addTileOverlay(TileOverlay tileOverlay) {
|
||||
final TileOverlayController controller = TileOverlayController(
|
||||
tileOverlay: tileOverlay,
|
||||
);
|
||||
_tileOverlays[tileOverlay.tileOverlayId] = controller;
|
||||
|
||||
if (tileOverlay.visible) {
|
||||
_insertZSorted(controller);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates [TileOverlay]s with new options.
|
||||
void changeTileOverlays(Set<TileOverlay> tileOverlays) {
|
||||
tileOverlays.forEach(_changeTileOverlay);
|
||||
}
|
||||
|
||||
void _changeTileOverlay(TileOverlay tileOverlay) {
|
||||
final TileOverlayController controller =
|
||||
_tileOverlays[tileOverlay.tileOverlayId]!;
|
||||
|
||||
final bool wasVisible = controller.tileOverlay.visible;
|
||||
final bool isVisible = tileOverlay.visible;
|
||||
|
||||
controller.update(tileOverlay);
|
||||
|
||||
if (wasVisible) {
|
||||
_remove(controller);
|
||||
}
|
||||
if (isVisible) {
|
||||
_insertZSorted(controller);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the tile overlays associated with the given [TileOverlayId]s.
|
||||
void removeTileOverlays(Set<TileOverlayId> tileOverlayIds) {
|
||||
tileOverlayIds.forEach(_removeTileOverlay);
|
||||
}
|
||||
|
||||
void _removeTileOverlay(TileOverlayId tileOverlayId) {
|
||||
final TileOverlayController? controller =
|
||||
_tileOverlays.remove(tileOverlayId);
|
||||
if (controller != null) {
|
||||
_remove(controller);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidates the tile overlay associated with the given [TileOverlayId].
|
||||
void clearTileCache(TileOverlayId tileOverlayId) {
|
||||
final TileOverlayController? controller = _tileOverlays[tileOverlayId];
|
||||
if (controller != null && controller.tileOverlay.visible) {
|
||||
final int i = _visibleTileOverlays.indexOf(controller);
|
||||
// This causes the map to reload the overlay.
|
||||
googleMap.overlayMapTypes!.setAt(i, controller.gmMapType);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ name: google_maps_flutter_web
|
||||
description: Web platform implementation of google_maps_flutter
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
|
||||
version: 0.5.2
|
||||
version: 0.5.3
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <4.0.0"
|
||||
@ -17,6 +17,7 @@ flutter:
|
||||
fileName: google_maps_flutter_web.dart
|
||||
|
||||
dependencies:
|
||||
collection: ^1.16.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_web_plugins:
|
||||
|
Reference in New Issue
Block a user