mirrored_repositories/awesome-flutter/examples/plugins/packages/google_maps_flutter/google_maps_flutter_web/lib
// 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 {
  // A cache of [MarkerController]s indexed by their [MarkerId].
  final Map<MarkerId, MarkerController> _markerIdToController;

  // The stream over which markers broadcast their events
  StreamController<MapEvent> _streamController;

  /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
  MarkersController({
    required StreamController<MapEvent> stream,
  })  : _streamController = stream,
        _markerIdToController = Map<MarkerId, MarkerController>();

  /// 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) {
    if (marker == null) {
      return;
    }

    final 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 is HtmlElement) {
        final content = infoWindowOptions.content as HtmlElement;
        content.onClick.listen((_) {
          _onInfoWindowTap(marker.markerId);
        });
      }
    }

    final currentMarker = _markerIdToController[marker.markerId]?.marker;

    final populationOptions = _markerOptionsFromMarker(marker, currentMarker);
    gmaps.Marker gmMarker = gmaps.Marker(populationOptions);
    gmMarker.map = googleMap;
    MarkerController controller = MarkerController(
      marker: gmMarker,
      infoWindow: gmInfoWindow,
      consumeTapEvents: marker.consumeTapEvents,
      onTap: () {
        this.showMarkerInfoWindow(marker.markerId);
        _onMarkerTap(marker.markerId);
      },
      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) {
    MarkerController? markerController = _markerIdToController[marker.markerId];
    if (markerController != null) {
      final markerOptions = _markerOptionsFromMarker(
        marker,
        markerController.marker,
      );
      final 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();
    MarkerController? markerController = _markerIdToController[markerId];
    markerController?.showInfoWindow();
  }

  /// Hides the [InfoWindow] of a [MarkerId].
  ///
  /// See also [showMarkerInfoWindow] and [isInfoWindowShown].
  void hideMarkerInfoWindow(MarkerId markerId) {
    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) {
    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 _onMarkerDragEnd(MarkerId markerId, gmaps.LatLng latLng) {
    _streamController.add(MarkerDragEndEvent(
      mapId,
      _gmLatLngToLatLng(latLng),
      markerId,
    ));
  }

  void _hideAllMarkerInfoWindow() {
    _markerIdToController.values
        .where((controller) =>
            controller == null ? false : controller.infoWindowShown)
        .forEach((controller) => controller.hideInfoWindow());
  }
}
