map: update clusters when idle, fixed cluster entry selection

This commit is contained in:
Thibault Deckers 2021-08-20 16:59:38 +09:00
parent 1f192e58f2
commit 42425f6fcf
5 changed files with 147 additions and 124 deletions

View file

@ -70,6 +70,7 @@ class Durations {
static const searchDebounceDelay = Duration(milliseconds: 250); static const searchDebounceDelay = Duration(milliseconds: 250);
static const contentChangeDebounceDelay = Duration(milliseconds: 1000); static const contentChangeDebounceDelay = Duration(milliseconds: 1000);
static const mapScrollDebounceDelay = Duration(milliseconds: 150); static const mapScrollDebounceDelay = Duration(milliseconds: 150);
static const mapIdleDebounceDelay = Duration(milliseconds: 100);
// app life // app life
static const lastVersionCheckInterval = Duration(days: 7); static const lastVersionCheckInterval = Duration(days: 7);

View file

@ -17,6 +17,7 @@ import 'package:aves/widgets/common/map/google/map.dart';
import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/map/leaflet/map.dart';
import 'package:aves/widgets/common/map/marker.dart'; import 'package:aves/widgets/common/map/marker.dart';
import 'package:aves/widgets/common/map/zoomed_bounds.dart'; import 'package:aves/widgets/common/map/zoomed_bounds.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:fluster/fluster.dart'; import 'package:fluster/fluster.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -83,9 +84,12 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
final onTap = widget.onMarkerTap; final onTap = widget.onMarkerTap;
if (onTap == null) return; if (onTap == null) return;
final geoEntries = <GeoEntry>[];
final clusterId = geoEntry.clusterId; final clusterId = geoEntry.clusterId;
if (clusterId != null) { Set<AvesEntry> getClusterEntries() {
if (clusterId == null) {
return {geoEntry.entry!};
}
var points = _defaultMarkerCluster.points(clusterId); var points = _defaultMarkerCluster.points(clusterId);
if (points.length != geoEntry.pointsSize) { if (points.length != geoEntry.pointsSize) {
// `Fluster.points()` method does not always return all the points contained in a cluster // `Fluster.points()` method does not always return all the points contained in a cluster
@ -94,11 +98,20 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
points = _slowMarkerCluster!.points(clusterId); points = _slowMarkerCluster!.points(clusterId);
assert(points.length == geoEntry.pointsSize, 'got ${points.length}/${geoEntry.pointsSize} for geoEntry=$geoEntry'); assert(points.length == geoEntry.pointsSize, 'got ${points.length}/${geoEntry.pointsSize} for geoEntry=$geoEntry');
} }
geoEntries.addAll(points); return points.map((geoEntry) => geoEntry.entry!).toSet();
} else { }
geoEntries.add(geoEntry);
AvesEntry? markerEntry;
if (clusterId != null) {
final uri = geoEntry.childMarkerId;
markerEntry = entries.firstWhereOrNull((v) => v.uri == uri);
} else {
markerEntry = geoEntry.entry;
}
if (markerEntry != null) {
onTap(markerEntry, getClusterEntries);
} }
onTap(geoEntries.map((geoEntry) => geoEntry.entry!).toList());
} }
return FutureBuilder<bool>( return FutureBuilder<bool>(
@ -110,7 +123,7 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
builder: (context, mapStyle, child) { builder: (context, mapStyle, child) {
final isGoogleMaps = mapStyle.isGoogleMaps; final isGoogleMaps = mapStyle.isGoogleMaps;
final progressive = !isGoogleMaps; final progressive = !isGoogleMaps;
Widget _buildMarker(MarkerKey key) => ImageMarker( Widget _buildMarkerWidget(MarkerKey key) => ImageMarker(
key: key, key: key,
entry: key.entry, entry: key.entry,
count: key.count, count: key.count,
@ -127,9 +140,8 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
minZoom: 0, minZoom: 0,
maxZoom: 20, maxZoom: 20,
style: mapStyle, style: mapStyle,
markerBuilder: _buildMarker, markerClusterBuilder: _buildMarkerClusters,
markerCluster: _defaultMarkerCluster, markerWidgetBuilder: _buildMarkerWidget,
markerEntries: entries,
onUserZoomChange: widget.onUserZoomChange, onUserZoomChange: widget.onUserZoomChange,
onMarkerTap: _onMarkerTap, onMarkerTap: _onMarkerTap,
) )
@ -140,9 +152,8 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
minZoom: 2, minZoom: 2,
maxZoom: 16, maxZoom: 16,
style: mapStyle, style: mapStyle,
markerBuilder: _buildMarker, markerClusterBuilder: _buildMarkerClusters,
markerCluster: _defaultMarkerCluster, markerWidgetBuilder: _buildMarkerWidget,
markerEntries: entries,
markerSize: Size( markerSize: Size(
GeoMap.markerImageExtent + ImageMarker.outerBorderWidth * 2, GeoMap.markerImageExtent + ImageMarker.outerBorderWidth * 2,
GeoMap.markerImageExtent + ImageMarker.outerBorderWidth * 2 + GeoMap.pointerSize.height, GeoMap.markerImageExtent + ImageMarker.outerBorderWidth * 2 + GeoMap.pointerSize.height,
@ -232,6 +243,19 @@ class _GeoMapState extends State<GeoMap> with TickerProviderStateMixin {
createCluster: (base, lng, lat) => GeoEntry.createCluster(base, lng, lat), createCluster: (base, lng, lat) => GeoEntry.createCluster(base, lng, lat),
); );
} }
Map<MarkerKey, GeoEntry> _buildMarkerClusters() {
final bounds = _boundsNotifier.value;
final geoEntries = _defaultMarkerCluster.clusters(bounds.boundingBox, bounds.zoom.round());
return Map.fromEntries(geoEntries.map((v) {
if (v.isCluster!) {
final uri = v.childMarkerId;
final entry = entries.firstWhere((v) => v.uri == uri);
return MapEntry(MarkerKey(entry, v.pointsSize), v);
}
return MapEntry(MarkerKey(v.entry!, null), v);
}));
}
} }
@immutable @immutable
@ -245,6 +269,7 @@ class MarkerKey extends LocalKey with EquatableMixin {
const MarkerKey(this.entry, this.count); const MarkerKey(this.entry, this.count);
} }
typedef EntryMarkerBuilder = Widget Function(MarkerKey key); typedef MarkerClusterBuilder = Map<MarkerKey, GeoEntry> Function();
typedef MarkerWidgetBuilder = Widget Function(MarkerKey key);
typedef UserZoomChangeCallback = void Function(double zoom); typedef UserZoomChangeCallback = void Function(double zoom);
typedef MarkerTapCallback = void Function(List<AvesEntry> entries); typedef MarkerTapCallback = void Function(AvesEntry markerEntry, Set<AvesEntry> Function() getClusterEntries);

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart'; import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/enums.dart';
import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/change_notifier.dart';
@ -12,8 +11,6 @@ import 'package:aves/widgets/common/map/geo_entry.dart';
import 'package:aves/widgets/common/map/geo_map.dart'; import 'package:aves/widgets/common/map/geo_map.dart';
import 'package:aves/widgets/common/map/google/marker_generator.dart'; import 'package:aves/widgets/common/map/google/marker_generator.dart';
import 'package:aves/widgets/common/map/zoomed_bounds.dart'; import 'package:aves/widgets/common/map/zoomed_bounds.dart';
import 'package:collection/collection.dart';
import 'package:fluster/fluster.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:latlong2/latlong.dart' as ll; import 'package:latlong2/latlong.dart' as ll;
@ -24,9 +21,8 @@ class EntryGoogleMap extends StatefulWidget {
final bool interactive; final bool interactive;
final double? minZoom, maxZoom; final double? minZoom, maxZoom;
final EntryMapStyle style; final EntryMapStyle style;
final EntryMarkerBuilder markerBuilder; final MarkerClusterBuilder markerClusterBuilder;
final Fluster<GeoEntry> markerCluster; final MarkerWidgetBuilder markerWidgetBuilder;
final List<AvesEntry> markerEntries;
final UserZoomChangeCallback? onUserZoomChange; final UserZoomChangeCallback? onUserZoomChange;
final void Function(GeoEntry geoEntry)? onMarkerTap; final void Function(GeoEntry geoEntry)? onMarkerTap;
@ -38,9 +34,8 @@ class EntryGoogleMap extends StatefulWidget {
this.minZoom, this.minZoom,
this.maxZoom, this.maxZoom,
required this.style, required this.style,
required this.markerBuilder, required this.markerClusterBuilder,
required this.markerCluster, required this.markerWidgetBuilder,
required this.markerEntries,
this.onUserZoomChange, this.onUserZoomChange,
this.onMarkerTap, this.onMarkerTap,
}) : super(key: key); }) : super(key: key);
@ -52,6 +47,7 @@ class EntryGoogleMap extends StatefulWidget {
class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObserver { class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObserver {
GoogleMapController? _googleMapController; GoogleMapController? _googleMapController;
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
Map<MarkerKey, GeoEntry> _geoEntryByMarkerKey = {};
final Map<MarkerKey, Uint8List> _markerBitmaps = {}; final Map<MarkerKey, Uint8List> _markerBitmaps = {};
final AChangeNotifier _markerBitmapChangeNotifier = AChangeNotifier(); final AChangeNotifier _markerBitmapChangeNotifier = AChangeNotifier();
@ -66,28 +62,34 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget);
}
@override
void didUpdateWidget(covariant EntryGoogleMap oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
}
@override
void dispose() {
_unregisterWidget(widget);
_googleMapController?.dispose();
super.dispose();
}
void _registerWidget(EntryGoogleMap widget) {
final avesMapController = widget.controller; final avesMapController = widget.controller;
if (avesMapController != null) { if (avesMapController != null) {
_subscriptions.add(avesMapController.moveEvents.listen((event) => _moveTo(_toGoogleLatLng(event.latLng)))); _subscriptions.add(avesMapController.moveEvents.listen((event) => _moveTo(_toGoogleLatLng(event.latLng))));
} }
} }
@override void _unregisterWidget(EntryGoogleMap widget) {
void didUpdateWidget(covariant EntryGoogleMap oldWidget) {
super.didUpdateWidget(oldWidget);
const eq = DeepCollectionEquality();
if (!eq.equals(widget.markerEntries, oldWidget.markerEntries)) {
_markerBitmaps.clear();
}
}
@override
void dispose() {
_googleMapController?.dispose();
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
super.dispose();
} }
@override @override
@ -107,51 +109,35 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<ZoomedBounds?>( return Stack(
valueListenable: boundsNotifier, children: [
builder: (context, visibleRegion, child) { MarkerGeneratorWidget<MarkerKey>(
final allEntries = widget.markerEntries; markers: _geoEntryByMarkerKey.keys.map(widget.markerWidgetBuilder).toList(),
final geoEntries = visibleRegion != null ? widget.markerCluster.clusters(visibleRegion.boundingBox, visibleRegion.zoom.round()) : <GeoEntry>[]; isReadyToRender: (key) => key.entry.isThumbnailReady(extent: GeoMap.markerImageExtent),
final geoEntryByMarkerKey = Map.fromEntries(geoEntries.map((v) { onRendered: (key, bitmap) {
if (v.isCluster!) { _markerBitmaps[key] = bitmap;
final uri = v.childMarkerId; _markerBitmapChangeNotifier.notifyListeners();
final entry = allEntries.firstWhere((v) => v.uri == uri); },
return MapEntry(MarkerKey(entry, v.pointsSize), v); ),
} MapDecorator(
return MapEntry(MarkerKey(v.entry!, null), v); interactive: interactive,
})); child: _buildMap(),
),
return Stack( MapButtonPanel(
children: [ boundsNotifier: boundsNotifier,
MarkerGeneratorWidget<MarkerKey>( zoomBy: _zoomBy,
markers: geoEntryByMarkerKey.keys.map(widget.markerBuilder).toList(), resetRotation: interactive ? _resetRotation : null,
isReadyToRender: (key) => key.entry.isThumbnailReady(extent: GeoMap.markerImageExtent), ),
onRendered: (key, bitmap) { ],
_markerBitmaps[key] = bitmap;
_markerBitmapChangeNotifier.notifyListeners();
},
),
MapDecorator(
interactive: interactive,
child: _buildMap(geoEntryByMarkerKey),
),
MapButtonPanel(
boundsNotifier: boundsNotifier,
zoomBy: _zoomBy,
resetRotation: interactive ? _resetRotation : null,
),
],
);
},
); );
} }
Widget _buildMap(Map<MarkerKey, GeoEntry> geoEntryByMarkerKey) { Widget _buildMap() {
return AnimatedBuilder( return AnimatedBuilder(
animation: _markerBitmapChangeNotifier, animation: _markerBitmapChangeNotifier,
builder: (context, child) { builder: (context, child) {
final markers = <Marker>{}; final markers = <Marker>{};
geoEntryByMarkerKey.forEach((markerKey, geoEntry) { _geoEntryByMarkerKey.forEach((markerKey, geoEntry) {
final bytes = _markerBitmaps[markerKey]; final bytes = _markerBitmaps[markerKey];
if (bytes != null) { if (bytes != null) {
final point = LatLng(geoEntry.latitude!, geoEntry.longitude!); final point = LatLng(geoEntry.latitude!, geoEntry.longitude!);
@ -195,11 +181,17 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
markers: markers, markers: markers,
onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing), onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing),
onCameraIdle: _updateClusters,
); );
}, },
); );
} }
void _updateClusters() {
if (!mounted) return;
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
}
Future<void> _updateVisibleRegion({required double zoom, required double rotation}) async { Future<void> _updateVisibleRegion({required double zoom, required double rotation}) async {
if (!mounted) return; if (!mounted) return;
@ -226,7 +218,6 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
final controller = _googleMapController; final controller = _googleMapController;
if (controller == null) return; if (controller == null) return;
final bounds = boundsNotifier.value;
await controller.animateCamera(CameraUpdate.newCameraPosition(CameraPosition( await controller.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: _toGoogleLatLng(bounds.center), target: _toGoogleLatLng(bounds.center),
zoom: bounds.zoom, zoom: bounds.zoom,

View file

@ -1,7 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/enums.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/common/map/buttons.dart'; import 'package:aves/widgets/common/map/buttons.dart';
import 'package:aves/widgets/common/map/controller.dart'; import 'package:aves/widgets/common/map/controller.dart';
import 'package:aves/widgets/common/map/decorator.dart'; import 'package:aves/widgets/common/map/decorator.dart';
@ -11,7 +12,6 @@ import 'package:aves/widgets/common/map/latlng_tween.dart';
import 'package:aves/widgets/common/map/leaflet/scale_layer.dart'; import 'package:aves/widgets/common/map/leaflet/scale_layer.dart';
import 'package:aves/widgets/common/map/leaflet/tile_layers.dart'; import 'package:aves/widgets/common/map/leaflet/tile_layers.dart';
import 'package:aves/widgets/common/map/zoomed_bounds.dart'; import 'package:aves/widgets/common/map/zoomed_bounds.dart';
import 'package:fluster/fluster.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@ -22,9 +22,8 @@ class EntryLeafletMap extends StatefulWidget {
final bool interactive; final bool interactive;
final double minZoom, maxZoom; final double minZoom, maxZoom;
final EntryMapStyle style; final EntryMapStyle style;
final EntryMarkerBuilder markerBuilder; final MarkerClusterBuilder markerClusterBuilder;
final Fluster<GeoEntry> markerCluster; final MarkerWidgetBuilder markerWidgetBuilder;
final List<AvesEntry> markerEntries;
final Size markerSize; final Size markerSize;
final UserZoomChangeCallback? onUserZoomChange; final UserZoomChangeCallback? onUserZoomChange;
final void Function(GeoEntry geoEntry)? onMarkerTap; final void Function(GeoEntry geoEntry)? onMarkerTap;
@ -37,9 +36,8 @@ class EntryLeafletMap extends StatefulWidget {
this.minZoom = 0, this.minZoom = 0,
this.maxZoom = 22, this.maxZoom = 22,
required this.style, required this.style,
required this.markerBuilder, required this.markerClusterBuilder,
required this.markerCluster, required this.markerWidgetBuilder,
required this.markerEntries,
required this.markerSize, required this.markerSize,
this.onUserZoomChange, this.onUserZoomChange,
this.onMarkerTap, this.onMarkerTap,
@ -52,6 +50,8 @@ class EntryLeafletMap extends StatefulWidget {
class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderStateMixin { class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderStateMixin {
final MapController _leafletMapController = MapController(); final MapController _leafletMapController = MapController();
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
Map<MarkerKey, GeoEntry> _geoEntryByMarkerKey = {};
final Debouncer _debouncer = Debouncer(delay: Durations.mapIdleDebounceDelay);
ValueNotifier<ZoomedBounds> get boundsNotifier => widget.boundsNotifier; ValueNotifier<ZoomedBounds> get boundsNotifier => widget.boundsNotifier;
@ -65,58 +65,59 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget);
WidgetsBinding.instance!.addPostFrameCallback((_) => _updateVisibleRegion());
}
@override
void didUpdateWidget(covariant EntryLeafletMap oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
}
@override
void dispose() {
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(EntryLeafletMap widget) {
final avesMapController = widget.controller; final avesMapController = widget.controller;
if (avesMapController != null) { if (avesMapController != null) {
_subscriptions.add(avesMapController.moveEvents.listen((event) => _moveTo(event.latLng))); _subscriptions.add(avesMapController.moveEvents.listen((event) => _moveTo(event.latLng)));
} }
_subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion())); _subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion()));
WidgetsBinding.instance!.addPostFrameCallback((_) => _updateVisibleRegion()); boundsNotifier.addListener(_onBoundsChange);
} }
@override void _unregisterWidget(EntryLeafletMap widget) {
void dispose() { boundsNotifier.removeListener(_onBoundsChange);
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<ZoomedBounds?>( return Stack(
valueListenable: boundsNotifier, children: [
builder: (context, visibleRegion, child) { MapDecorator(
final allEntries = widget.markerEntries; interactive: interactive,
final geoEntries = visibleRegion != null ? widget.markerCluster.clusters(visibleRegion.boundingBox, visibleRegion.zoom.round()) : <GeoEntry>[]; child: _buildMap(),
final geoEntryByMarkerKey = Map.fromEntries(geoEntries.map((v) { ),
if (v.isCluster!) { MapButtonPanel(
final uri = v.childMarkerId; boundsNotifier: boundsNotifier,
final entry = allEntries.firstWhere((v) => v.uri == uri); zoomBy: _zoomBy,
return MapEntry(MarkerKey(entry, v.pointsSize), v); resetRotation: interactive ? _resetRotation : null,
} ),
return MapEntry(MarkerKey(v.entry!, null), v); ],
}));
return Stack(
children: [
MapDecorator(
interactive: interactive,
child: _buildMap(geoEntryByMarkerKey),
),
MapButtonPanel(
boundsNotifier: boundsNotifier,
zoomBy: _zoomBy,
resetRotation: interactive ? _resetRotation : null,
),
],
);
},
); );
} }
Widget _buildMap(Map<MarkerKey, GeoEntry> geoEntryByMarkerKey) { Widget _buildMap() {
final markerSize = widget.markerSize; final markerSize = widget.markerSize;
final markers = geoEntryByMarkerKey.entries.map((kv) { final markers = _geoEntryByMarkerKey.entries.map((kv) {
final markerKey = kv.key; final markerKey = kv.key;
final geoEntry = kv.value; final geoEntry = kv.value;
final latLng = LatLng(geoEntry.latitude!, geoEntry.longitude!); final latLng = LatLng(geoEntry.latitude!, geoEntry.longitude!);
@ -124,7 +125,7 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
point: latLng, point: latLng,
builder: (context) => GestureDetector( builder: (context) => GestureDetector(
onTap: () => widget.onMarkerTap?.call(geoEntry), onTap: () => widget.onMarkerTap?.call(geoEntry),
child: widget.markerBuilder(markerKey), child: widget.markerWidgetBuilder(markerKey),
), ),
width: markerSize.width, width: markerSize.width,
height: markerSize.height, height: markerSize.height,
@ -173,6 +174,13 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
} }
} }
void _onBoundsChange() => _debouncer(_updateClusters);
void _updateClusters() {
if (!mounted) return;
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
}
void _updateVisibleRegion() { void _updateVisibleRegion() {
final bounds = _leafletMapController.bounds; final bounds = _leafletMapController.bounds;
if (bounds != null) { if (bounds != null) {

View file

@ -74,14 +74,12 @@ class _MapPageState extends State<MapPage> {
entries: entries, entries: entries,
interactive: true, interactive: true,
isAnimatingNotifier: _isAnimatingNotifier, isAnimatingNotifier: _isAnimatingNotifier,
onMarkerTap: (markerEntries) { onMarkerTap: (markerEntry, getClusterEntries) {
if (markerEntries.isEmpty) return; final index = entries.indexOf(markerEntry);
final entry = markerEntries.first;
final index = entries.indexOf(entry);
if (_selectedIndexNotifier.value != index) { if (_selectedIndexNotifier.value != index) {
_selectedIndexNotifier.value = index; _selectedIndexNotifier.value = index;
} else { } else {
_moveToEntry(entry); _moveToEntry(markerEntry);
} }
}, },
), ),