map: update on entry removal
This commit is contained in:
parent
d0c62a602b
commit
8df538e7f7
8 changed files with 152 additions and 82 deletions
|
@ -46,8 +46,8 @@ class CollectionLens with ChangeNotifier {
|
||||||
id ??= hashCode;
|
id ??= hashCode;
|
||||||
if (listenToSource) {
|
if (listenToSource) {
|
||||||
final sourceEvents = source.eventBus;
|
final sourceEvents = source.eventBus;
|
||||||
_subscriptions.add(sourceEvents.on<EntryAddedEvent>().listen((e) => onEntryAdded(e.entries)));
|
_subscriptions.add(sourceEvents.on<EntryAddedEvent>().listen((e) => _onEntryAdded(e.entries)));
|
||||||
_subscriptions.add(sourceEvents.on<EntryRemovedEvent>().listen((e) => onEntryRemoved(e.entries)));
|
_subscriptions.add(sourceEvents.on<EntryRemovedEvent>().listen((e) => _onEntryRemoved(e.entries)));
|
||||||
_subscriptions.add(sourceEvents.on<EntryMovedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<EntryMovedEvent>().listen((e) => _refresh()));
|
||||||
_subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => _refresh()));
|
||||||
_subscriptions.add(sourceEvents.on<FilterVisibilityChangedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<FilterVisibilityChangedEvent>().listen((e) => _refresh()));
|
||||||
|
@ -73,6 +73,20 @@ class CollectionLens with ChangeNotifier {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CollectionLens copyWith({
|
||||||
|
CollectionSource? source,
|
||||||
|
Set<CollectionFilter>? filters,
|
||||||
|
bool? listenToSource,
|
||||||
|
List<AvesEntry>? fixedSelection,
|
||||||
|
}) =>
|
||||||
|
CollectionLens(
|
||||||
|
source: source ?? this.source,
|
||||||
|
filters: filters ?? this.filters,
|
||||||
|
id: id,
|
||||||
|
listenToSource: listenToSource ?? this.listenToSource,
|
||||||
|
fixedSelection: fixedSelection ?? this.fixedSelection,
|
||||||
|
);
|
||||||
|
|
||||||
bool get isEmpty => _filteredSortedEntries.isEmpty;
|
bool get isEmpty => _filteredSortedEntries.isEmpty;
|
||||||
|
|
||||||
int get entryCount => _filteredSortedEntries.length;
|
int get entryCount => _filteredSortedEntries.length;
|
||||||
|
@ -103,16 +117,16 @@ class CollectionLens with ChangeNotifier {
|
||||||
filters.removeWhere((old) => old.category == filter.category);
|
filters.removeWhere((old) => old.category == filter.category);
|
||||||
}
|
}
|
||||||
filters.add(filter);
|
filters.add(filter);
|
||||||
onFilterChanged();
|
_onFilterChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeFilter(CollectionFilter filter) {
|
void removeFilter(CollectionFilter filter) {
|
||||||
if (!filters.contains(filter)) return;
|
if (!filters.contains(filter)) return;
|
||||||
filters.remove(filter);
|
filters.remove(filter);
|
||||||
onFilterChanged();
|
_onFilterChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFilterChanged() {
|
void _onFilterChanged() {
|
||||||
_refresh();
|
_refresh();
|
||||||
filterChangeNotifier.notifyListeners();
|
filterChangeNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -229,11 +243,11 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEntryAdded(Set<AvesEntry>? entries) {
|
void _onEntryAdded(Set<AvesEntry>? entries) {
|
||||||
_refresh();
|
_refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEntryRemoved(Set<AvesEntry> entries) {
|
void _onEntryRemoved(Set<AvesEntry> entries) {
|
||||||
if (groupBursts) {
|
if (groupBursts) {
|
||||||
// find impacted burst groups
|
// find impacted burst groups
|
||||||
final obsoleteBurstEntries = <AvesEntry>{};
|
final obsoleteBurstEntries = <AvesEntry>{};
|
||||||
|
@ -256,6 +270,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
// we should remove obsolete entries and sections
|
// we should remove obsolete entries and sections
|
||||||
// but do not apply sort/section
|
// but do not apply sort/section
|
||||||
// as section order change would surprise the user while browsing
|
// as section order change would surprise the user while browsing
|
||||||
|
fixedSelection?.removeWhere(entries.contains);
|
||||||
_filteredSortedEntries.removeWhere(entries.contains);
|
_filteredSortedEntries.removeWhere(entries.contains);
|
||||||
_sortedEntries?.removeWhere(entries.contains);
|
_sortedEntries?.removeWhere(entries.contains);
|
||||||
sections.forEach((key, sectionEntries) => sectionEntries.removeWhere(entries.contains));
|
sections.forEach((key, sectionEntries) => sectionEntries.removeWhere(entries.contains));
|
||||||
|
|
|
@ -288,6 +288,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: MapPage.routeName),
|
settings: const RouteSettings(name: MapPage.routeName),
|
||||||
builder: (context) => MapPage(
|
builder: (context) => MapPage(
|
||||||
|
// need collection with fresh ID to prevent hero from scroller on Map page to Collection page
|
||||||
collection: CollectionLens(
|
collection: CollectionLens(
|
||||||
source: collection.source,
|
source: collection.source,
|
||||||
filters: collection.filters,
|
filters: collection.filters,
|
||||||
|
|
|
@ -73,10 +73,7 @@ class InteractiveThumbnail extends StatelessWidget {
|
||||||
TransparentMaterialPageRoute(
|
TransparentMaterialPageRoute(
|
||||||
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
||||||
pageBuilder: (context, a, sa) {
|
pageBuilder: (context, a, sa) {
|
||||||
final viewerCollection = CollectionLens(
|
final viewerCollection = collection.copyWith(
|
||||||
source: collection.source,
|
|
||||||
filters: collection.filters,
|
|
||||||
id: collection.id,
|
|
||||||
listenToSource: false,
|
listenToSource: false,
|
||||||
);
|
);
|
||||||
assert(viewerCollection.sortedEntries.map((e) => e.contentId).contains(entry.contentId));
|
assert(viewerCollection.sortedEntries.map((e) => e.contentId).contains(entry.contentId));
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/settings/map_style.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
import 'package:aves/utils/change_notifier.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/utils/math_utils.dart';
|
import 'package:aves/utils/math_utils.dart';
|
||||||
import 'package:aves/widgets/common/map/attribution.dart';
|
import 'package:aves/widgets/common/map/attribution.dart';
|
||||||
|
@ -27,6 +28,7 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class GeoMap extends StatefulWidget {
|
class GeoMap extends StatefulWidget {
|
||||||
final AvesMapController? controller;
|
final AvesMapController? controller;
|
||||||
|
final Listenable? collectionListenable;
|
||||||
final List<AvesEntry> entries;
|
final List<AvesEntry> entries;
|
||||||
final AvesEntry? initialEntry;
|
final AvesEntry? initialEntry;
|
||||||
final ValueNotifier<bool> isAnimatingNotifier;
|
final ValueNotifier<bool> isAnimatingNotifier;
|
||||||
|
@ -42,6 +44,7 @@ class GeoMap extends StatefulWidget {
|
||||||
const GeoMap({
|
const GeoMap({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.controller,
|
this.controller,
|
||||||
|
this.collectionListenable,
|
||||||
required this.entries,
|
required this.entries,
|
||||||
this.initialEntry,
|
this.initialEntry,
|
||||||
required this.isAnimatingNotifier,
|
required this.isAnimatingNotifier,
|
||||||
|
@ -63,8 +66,9 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
// so we prevent loading it while scrolling or animating
|
// so we prevent loading it while scrolling or animating
|
||||||
bool _googleMapsLoaded = false;
|
bool _googleMapsLoaded = false;
|
||||||
late final ValueNotifier<ZoomedBounds> _boundsNotifier;
|
late final ValueNotifier<ZoomedBounds> _boundsNotifier;
|
||||||
late final Fluster<GeoEntry> _defaultMarkerCluster;
|
Fluster<GeoEntry>? _defaultMarkerCluster;
|
||||||
Fluster<GeoEntry>? _slowMarkerCluster;
|
Fluster<GeoEntry>? _slowMarkerCluster;
|
||||||
|
final AChangeNotifier _clusterChangeNotifier = AChangeNotifier();
|
||||||
|
|
||||||
List<AvesEntry> get entries => widget.entries;
|
List<AvesEntry> get entries => widget.entries;
|
||||||
|
|
||||||
|
@ -84,7 +88,29 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
_boundsNotifier = ValueNotifier(bounds.copyWith(
|
_boundsNotifier = ValueNotifier(bounds.copyWith(
|
||||||
zoom: max(bounds.zoom, minInitialZoom),
|
zoom: max(bounds.zoom, minInitialZoom),
|
||||||
));
|
));
|
||||||
_defaultMarkerCluster = _buildFluster();
|
_registerWidget(widget);
|
||||||
|
_onCollectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant GeoMap oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
_unregisterWidget(oldWidget);
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unregisterWidget(widget);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerWidget(GeoMap widget) {
|
||||||
|
widget.collectionListenable?.addListener(_onCollectionChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unregisterWidget(GeoMap widget) {
|
||||||
|
widget.collectionListenable?.removeListener(_onCollectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -99,7 +125,7 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
return {geoEntry.entry!};
|
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
|
||||||
// the higher `nodeSize` is, the higher the chance to get all the points (i.e. as many as the cluster `pointsSize`)
|
// the higher `nodeSize` is, the higher the chance to get all the points (i.e. as many as the cluster `pointsSize`)
|
||||||
|
@ -144,6 +170,7 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
Widget child = isGoogleMaps
|
Widget child = isGoogleMaps
|
||||||
? EntryGoogleMap(
|
? EntryGoogleMap(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
|
clusterListenable: _clusterChangeNotifier,
|
||||||
boundsNotifier: _boundsNotifier,
|
boundsNotifier: _boundsNotifier,
|
||||||
minZoom: 0,
|
minZoom: 0,
|
||||||
maxZoom: 20,
|
maxZoom: 20,
|
||||||
|
@ -158,6 +185,7 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
)
|
)
|
||||||
: EntryLeafletMap(
|
: EntryLeafletMap(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
|
clusterListenable: _clusterChangeNotifier,
|
||||||
boundsNotifier: _boundsNotifier,
|
boundsNotifier: _boundsNotifier,
|
||||||
minZoom: 2,
|
minZoom: 2,
|
||||||
maxZoom: 16,
|
maxZoom: 16,
|
||||||
|
@ -237,6 +265,12 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onCollectionChanged() {
|
||||||
|
_defaultMarkerCluster = _buildFluster();
|
||||||
|
_slowMarkerCluster = null;
|
||||||
|
_clusterChangeNotifier.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Fluster<GeoEntry> _buildFluster({int nodeSize = 64}) {
|
Fluster<GeoEntry> _buildFluster({int nodeSize = 64}) {
|
||||||
final markers = entries.map((entry) {
|
final markers = entries.map((entry) {
|
||||||
final latLng = entry.latLng!;
|
final latLng = entry.latLng!;
|
||||||
|
@ -266,7 +300,7 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
|
|
||||||
Map<MarkerKey, GeoEntry> _buildMarkerClusters() {
|
Map<MarkerKey, GeoEntry> _buildMarkerClusters() {
|
||||||
final bounds = _boundsNotifier.value;
|
final bounds = _boundsNotifier.value;
|
||||||
final geoEntries = _defaultMarkerCluster.clusters(bounds.boundingBox, bounds.zoom.round());
|
final geoEntries = _defaultMarkerCluster?.clusters(bounds.boundingBox, bounds.zoom.round()) ?? [];
|
||||||
return Map.fromEntries(geoEntries.map((v) {
|
return Map.fromEntries(geoEntries.map((v) {
|
||||||
if (v.isCluster!) {
|
if (v.isCluster!) {
|
||||||
final uri = v.childMarkerId;
|
final uri = v.childMarkerId;
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class EntryGoogleMap extends StatefulWidget {
|
class EntryGoogleMap extends StatefulWidget {
|
||||||
final AvesMapController? controller;
|
final AvesMapController? controller;
|
||||||
|
final Listenable clusterListenable;
|
||||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||||
final double? minZoom, maxZoom;
|
final double? minZoom, maxZoom;
|
||||||
final EntryMapStyle style;
|
final EntryMapStyle style;
|
||||||
|
@ -35,6 +36,7 @@ class EntryGoogleMap extends StatefulWidget {
|
||||||
const EntryGoogleMap({
|
const EntryGoogleMap({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.controller,
|
this.controller,
|
||||||
|
required this.clusterListenable,
|
||||||
required this.boundsNotifier,
|
required this.boundsNotifier,
|
||||||
this.minZoom,
|
this.minZoom,
|
||||||
this.maxZoom,
|
this.maxZoom,
|
||||||
|
@ -93,9 +95,11 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
||||||
if (avesMapController != null) {
|
if (avesMapController != null) {
|
||||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toGoogleLatLng(event.latLng))));
|
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toGoogleLatLng(event.latLng))));
|
||||||
}
|
}
|
||||||
|
widget.clusterListenable.addListener(_updateMarkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(EntryGoogleMap widget) {
|
void _unregisterWidget(EntryGoogleMap widget) {
|
||||||
|
widget.clusterListenable.removeListener(_updateMarkers);
|
||||||
_subscriptions
|
_subscriptions
|
||||||
..forEach((sub) => sub.cancel())
|
..forEach((sub) => sub.cancel())
|
||||||
..clear();
|
..clear();
|
||||||
|
@ -167,53 +171,54 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
||||||
|
|
||||||
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
|
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
|
||||||
return ValueListenableBuilder<AvesEntry?>(
|
return ValueListenableBuilder<AvesEntry?>(
|
||||||
valueListenable: widget.dotEntryNotifier ?? ValueNotifier(null),
|
valueListenable: widget.dotEntryNotifier ?? ValueNotifier(null),
|
||||||
builder: (context, dotEntry, child) {
|
builder: (context, dotEntry, child) {
|
||||||
return GoogleMap(
|
return GoogleMap(
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
bearing: -bounds.rotation,
|
bearing: -bounds.rotation,
|
||||||
target: _toGoogleLatLng(bounds.center),
|
target: _toGoogleLatLng(bounds.center),
|
||||||
zoom: bounds.zoom,
|
zoom: bounds.zoom,
|
||||||
),
|
),
|
||||||
onMapCreated: (controller) async {
|
onMapCreated: (controller) async {
|
||||||
_googleMapController = controller;
|
_googleMapController = controller;
|
||||||
final zoom = await controller.getZoomLevel();
|
final zoom = await controller.getZoomLevel();
|
||||||
await _updateVisibleRegion(zoom: zoom, rotation: bounds.rotation);
|
await _updateVisibleRegion(zoom: zoom, rotation: bounds.rotation);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
// compass disabled to use provider agnostic controls
|
// compass disabled to use provider agnostic controls
|
||||||
compassEnabled: false,
|
compassEnabled: false,
|
||||||
mapToolbarEnabled: false,
|
mapToolbarEnabled: false,
|
||||||
mapType: _toMapType(widget.style),
|
mapType: _toMapType(widget.style),
|
||||||
minMaxZoomPreference: MinMaxZoomPreference(widget.minZoom, widget.maxZoom),
|
minMaxZoomPreference: MinMaxZoomPreference(widget.minZoom, widget.maxZoom),
|
||||||
rotateGesturesEnabled: true,
|
rotateGesturesEnabled: true,
|
||||||
scrollGesturesEnabled: interactive,
|
scrollGesturesEnabled: interactive,
|
||||||
// zoom controls disabled to use provider agnostic controls
|
// zoom controls disabled to use provider agnostic controls
|
||||||
zoomControlsEnabled: false,
|
zoomControlsEnabled: false,
|
||||||
zoomGesturesEnabled: interactive,
|
zoomGesturesEnabled: interactive,
|
||||||
// lite mode disabled because it lacks camera animation
|
// lite mode disabled because it lacks camera animation
|
||||||
liteModeEnabled: false,
|
liteModeEnabled: false,
|
||||||
// tilt disabled to match leaflet
|
// tilt disabled to match leaflet
|
||||||
tiltGesturesEnabled: false,
|
tiltGesturesEnabled: false,
|
||||||
myLocationEnabled: false,
|
myLocationEnabled: false,
|
||||||
myLocationButtonEnabled: false,
|
myLocationButtonEnabled: false,
|
||||||
markers: {
|
markers: {
|
||||||
...markers,
|
...markers,
|
||||||
if (dotEntry != null && _dotMarkerBitmap != null)
|
if (dotEntry != null && _dotMarkerBitmap != null)
|
||||||
Marker(
|
Marker(
|
||||||
markerId: const MarkerId('dot'),
|
markerId: const MarkerId('dot'),
|
||||||
anchor: const Offset(.5, .5),
|
anchor: const Offset(.5, .5),
|
||||||
consumeTapEvents: true,
|
consumeTapEvents: true,
|
||||||
icon: BitmapDescriptor.fromBytes(_dotMarkerBitmap!),
|
icon: BitmapDescriptor.fromBytes(_dotMarkerBitmap!),
|
||||||
position: _toGoogleLatLng(dotEntry.latLng!),
|
position: _toGoogleLatLng(dotEntry.latLng!),
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing),
|
onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing),
|
||||||
onCameraIdle: _onIdle,
|
onCameraIdle: _onIdle,
|
||||||
onTap: (position) => widget.onMapTap?.call(),
|
onTap: (position) => widget.onMapTap?.call(),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -221,6 +226,10 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
||||||
void _onIdle() {
|
void _onIdle() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
widget.controller?.notifyIdle(bounds);
|
widget.controller?.notifyIdle(bounds);
|
||||||
|
_updateMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateMarkers() {
|
||||||
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
|
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class EntryLeafletMap extends StatefulWidget {
|
class EntryLeafletMap extends StatefulWidget {
|
||||||
final AvesMapController? controller;
|
final AvesMapController? controller;
|
||||||
|
final Listenable clusterListenable;
|
||||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||||
final double minZoom, maxZoom;
|
final double minZoom, maxZoom;
|
||||||
final EntryMapStyle style;
|
final EntryMapStyle style;
|
||||||
|
@ -38,6 +39,7 @@ class EntryLeafletMap extends StatefulWidget {
|
||||||
const EntryLeafletMap({
|
const EntryLeafletMap({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.controller,
|
this.controller,
|
||||||
|
required this.clusterListenable,
|
||||||
required this.boundsNotifier,
|
required this.boundsNotifier,
|
||||||
this.minZoom = 0,
|
this.minZoom = 0,
|
||||||
this.maxZoom = 22,
|
this.maxZoom = 22,
|
||||||
|
@ -96,11 +98,13 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
|
||||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(event.latLng)));
|
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(event.latLng)));
|
||||||
}
|
}
|
||||||
_subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion()));
|
_subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion()));
|
||||||
boundsNotifier.addListener(_onBoundsChange);
|
widget.clusterListenable.addListener(_updateMarkers);
|
||||||
|
widget.boundsNotifier.addListener(_onBoundsChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(EntryLeafletMap widget) {
|
void _unregisterWidget(EntryLeafletMap widget) {
|
||||||
boundsNotifier.removeListener(_onBoundsChange);
|
widget.clusterListenable.removeListener(_updateMarkers);
|
||||||
|
widget.boundsNotifier.removeListener(_onBoundsChange);
|
||||||
_subscriptions
|
_subscriptions
|
||||||
..forEach((sub) => sub.cancel())
|
..forEach((sub) => sub.cancel())
|
||||||
..clear();
|
..clear();
|
||||||
|
@ -216,6 +220,10 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
|
||||||
void _onIdle() {
|
void _onIdle() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
widget.controller?.notifyIdle(bounds);
|
widget.controller?.notifyIdle(bounds);
|
||||||
|
_updateMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateMarkers() {
|
||||||
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
|
setState(() => _geoEntryByMarkerKey = widget.markerClusterBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,7 @@ class _MapPageContentState extends State<MapPageContent> with SingleTickerProvid
|
||||||
// key is expected by test driver
|
// key is expected by test driver
|
||||||
key: const Key('map_view'),
|
key: const Key('map_view'),
|
||||||
controller: _mapController,
|
controller: _mapController,
|
||||||
|
collectionListenable: openingCollection,
|
||||||
entries: openingCollection.sortedEntries,
|
entries: openingCollection.sortedEntries,
|
||||||
initialEntry: widget.initialEntry,
|
initialEntry: widget.initialEntry,
|
||||||
isAnimatingNotifier: _isPageAnimatingNotifier,
|
isAnimatingNotifier: _isPageAnimatingNotifier,
|
||||||
|
@ -255,15 +256,21 @@ class _MapPageContentState extends State<MapPageContent> with SingleTickerProvid
|
||||||
builder: (context, mqWidth, child) => ValueListenableBuilder<CollectionLens?>(
|
builder: (context, mqWidth, child) => ValueListenableBuilder<CollectionLens?>(
|
||||||
valueListenable: _regionCollectionNotifier,
|
valueListenable: _regionCollectionNotifier,
|
||||||
builder: (context, regionCollection, child) {
|
builder: (context, regionCollection, child) {
|
||||||
final regionEntries = regionCollection?.sortedEntries ?? [];
|
return AnimatedBuilder(
|
||||||
return ThumbnailScroller(
|
// update when entries are added/removed
|
||||||
availableWidth: mqWidth,
|
animation: regionCollection ?? ChangeNotifier(),
|
||||||
entryCount: regionEntries.length,
|
builder: (context, child) {
|
||||||
entryBuilder: (index) => index < regionEntries.length ? regionEntries[index] : null,
|
final regionEntries = regionCollection?.sortedEntries ?? [];
|
||||||
indexNotifier: _selectedIndexNotifier,
|
return ThumbnailScroller(
|
||||||
onTap: _onThumbnailTap,
|
availableWidth: mqWidth,
|
||||||
heroTagger: (entry) => Object.hashAll([regionCollection?.id, entry.uri]),
|
entryCount: regionEntries.length,
|
||||||
highlightable: true,
|
entryBuilder: (index) => index < regionEntries.length ? regionEntries[index] : null,
|
||||||
|
indexNotifier: _selectedIndexNotifier,
|
||||||
|
onTap: _onThumbnailTap,
|
||||||
|
heroTagger: (entry) => Object.hashAll([regionCollection?.id, entry.uri]),
|
||||||
|
highlightable: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -295,14 +302,11 @@ class _MapPageContentState extends State<MapPageContent> with SingleTickerProvid
|
||||||
selectedEntry = selectedIndex != null && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null;
|
selectedEntry = selectedIndex != null && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_regionCollectionNotifier.value = CollectionLens(
|
_regionCollectionNotifier.value = openingCollection.copyWith(
|
||||||
source: openingCollection.source,
|
filters: {
|
||||||
listenToSource: false,
|
|
||||||
fixedSelection: openingCollection.sortedEntries,
|
|
||||||
filters: [
|
|
||||||
...openingCollection.filters.whereNot((v) => v is CoordinateFilter),
|
...openingCollection.filters.whereNot((v) => v is CoordinateFilter),
|
||||||
CoordinateFilter(bounds.sw, bounds.ne),
|
CoordinateFilter(bounds.sw, bounds.ne),
|
||||||
],
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// get entries from the new collection, so the entry order is the same
|
// get entries from the new collection, so the entry order is the same
|
||||||
|
@ -352,7 +356,9 @@ class _MapPageContentState extends State<MapPageContent> with SingleTickerProvid
|
||||||
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
||||||
pageBuilder: (context, a, sa) {
|
pageBuilder: (context, a, sa) {
|
||||||
return EntryViewerPage(
|
return EntryViewerPage(
|
||||||
collection: regionCollection,
|
collection: regionCollection?.copyWith(
|
||||||
|
listenToSource: false,
|
||||||
|
),
|
||||||
initialEntry: initialEntry,
|
initialEntry: initialEntry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -126,8 +126,8 @@ class _LocationSectionState extends State<LocationSection> {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: MapPage.routeName),
|
settings: const RouteSettings(name: MapPage.routeName),
|
||||||
builder: (context) => MapPage(
|
builder: (context) => MapPage(
|
||||||
collection: CollectionLens(
|
collection: baseCollection.copyWith(
|
||||||
source: baseCollection.source,
|
listenToSource: true,
|
||||||
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(),
|
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(),
|
||||||
),
|
),
|
||||||
initialEntry: entry,
|
initialEntry: entry,
|
||||||
|
|
Loading…
Reference in a new issue