blob: 0c378f9daba374ab339f9a3e7e27b7d08d759e66 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of google_maps_flutter_web;
/// This class manages a set of [MarkerController]s associated to a [GoogleMapController].
class MarkersController extends GeometryController {
/// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
MarkersController({
required StreamController<MapEvent<Object?>> stream,
}) : _streamController = stream,
_markerIdToController = <MarkerId, MarkerController>{};
// A cache of [MarkerController]s indexed by their [MarkerId].
final Map<MarkerId, MarkerController> _markerIdToController;
// The stream over which markers broadcast their events
final StreamController<MapEvent<Object?>> _streamController;
/// Returns the cache of [MarkerController]s. Test only.
@visibleForTesting
Map<MarkerId, MarkerController> get markers => _markerIdToController;
/// Adds a set of [Marker] objects to the cache.
///
/// Wraps each [Marker] into its corresponding [MarkerController].
void addMarkers(Set<Marker> markersToAdd) {
markersToAdd.forEach(_addMarker);
}
void _addMarker(Marker marker) {
final gmaps.InfoWindowOptions? infoWindowOptions =
_infoWindowOptionsFromMarker(marker);
gmaps.InfoWindow? gmInfoWindow;
if (infoWindowOptions != null) {
gmInfoWindow = gmaps.InfoWindow(infoWindowOptions);
// Google Maps' JS SDK does not have a click event on the InfoWindow, so
// we make one...
if (infoWindowOptions.content != null &&
infoWindowOptions.content is HtmlElement) {
final HtmlElement content = infoWindowOptions.content! as HtmlElement;
content.onClick.listen((_) {
_onInfoWindowTap(marker.markerId);
});
}
}
final gmaps.Marker? currentMarker =
_markerIdToController[marker.markerId]?.marker;
final gmaps.MarkerOptions markerOptions =
_markerOptionsFromMarker(marker, currentMarker);
final gmaps.Marker gmMarker = gmaps.Marker(markerOptions)..map = googleMap;
final MarkerController controller = MarkerController(
marker: gmMarker,
infoWindow: gmInfoWindow,
consumeTapEvents: marker.consumeTapEvents,
onTap: () {
showMarkerInfoWindow(marker.markerId);
_onMarkerTap(marker.markerId);
},
onDragStart: (gmaps.LatLng latLng) {
_onMarkerDragStart(marker.markerId, latLng);
},
onDrag: (gmaps.LatLng latLng) {
_onMarkerDrag(marker.markerId, latLng);
},
onDragEnd: (gmaps.LatLng latLng) {
_onMarkerDragEnd(marker.markerId, latLng);
},
);
_markerIdToController[marker.markerId] = controller;
}
/// Updates a set of [Marker] objects with new options.
void changeMarkers(Set<Marker> markersToChange) {
markersToChange.forEach(_changeMarker);
}
void _changeMarker(Marker marker) {
final MarkerController? markerController =
_markerIdToController[marker.markerId];
if (markerController != null) {
final gmaps.MarkerOptions markerOptions = _markerOptionsFromMarker(
marker,
markerController.marker,
);
final gmaps.InfoWindowOptions? infoWindow =
_infoWindowOptionsFromMarker(marker);
markerController.update(
markerOptions,
newInfoWindowContent: infoWindow?.content as HtmlElement?,
);
}
}
/// Removes a set of [MarkerId]s from the cache.
void removeMarkers(Set<MarkerId> markerIdsToRemove) {
markerIdsToRemove.forEach(_removeMarker);
}
void _removeMarker(MarkerId markerId) {
final MarkerController? markerController = _markerIdToController[markerId];
markerController?.remove();
_markerIdToController.remove(markerId);
}
// InfoWindow...
/// Shows the [InfoWindow] of a [MarkerId].
///
/// See also [hideMarkerInfoWindow] and [isInfoWindowShown].
void showMarkerInfoWindow(MarkerId markerId) {
_hideAllMarkerInfoWindow();
final MarkerController? markerController = _markerIdToController[markerId];
markerController?.showInfoWindow();
}
/// Hides the [InfoWindow] of a [MarkerId].
///
/// See also [showMarkerInfoWindow] and [isInfoWindowShown].
void hideMarkerInfoWindow(MarkerId markerId) {
final MarkerController? markerController = _markerIdToController[markerId];
markerController?.hideInfoWindow();
}
/// Returns whether or not the [InfoWindow] of a [MarkerId] is shown.
///
/// See also [showMarkerInfoWindow] and [hideMarkerInfoWindow].
bool isInfoWindowShown(MarkerId markerId) {
final MarkerController? markerController = _markerIdToController[markerId];
return markerController?.infoWindowShown ?? false;
}
// Handle internal events
bool _onMarkerTap(MarkerId markerId) {
// Have you ended here on your debugging? Is this wrong?
// Comment here: https://github.com/flutter/flutter/issues/64084
_streamController.add(MarkerTapEvent(mapId, markerId));
return _markerIdToController[markerId]?.consumeTapEvents ?? false;
}
void _onInfoWindowTap(MarkerId markerId) {
_streamController.add(InfoWindowTapEvent(mapId, markerId));
}
void _onMarkerDragStart(MarkerId markerId, gmaps.LatLng latLng) {
_streamController.add(MarkerDragStartEvent(
mapId,
_gmLatLngToLatLng(latLng),
markerId,
));
}
void _onMarkerDrag(MarkerId markerId, gmaps.LatLng latLng) {
_streamController.add(MarkerDragEvent(
mapId,
_gmLatLngToLatLng(latLng),
markerId,
));
}
void _onMarkerDragEnd(MarkerId markerId, gmaps.LatLng latLng) {
_streamController.add(MarkerDragEndEvent(
mapId,
_gmLatLngToLatLng(latLng),
markerId,
));
}
void _hideAllMarkerInfoWindow() {
_markerIdToController.values
.where((MarkerController? controller) =>
controller?.infoWindowShown ?? false)
.forEach((MarkerController controller) {
controller.hideInfoWindow();
});
}
}