2023-06-04 18:29:03 +00:00
|
|
|
import "package:flutter/material.dart";
|
|
|
|
import "package:flutter_map/flutter_map.dart";
|
|
|
|
import "package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart";
|
|
|
|
import "package:latlong2/latlong.dart";
|
|
|
|
import "package:photos/ui/map/image_marker.dart";
|
|
|
|
import "package:photos/ui/map/map_button.dart";
|
|
|
|
import 'package:photos/ui/map/map_gallery_tile.dart';
|
|
|
|
import 'package:photos/ui/map/map_gallery_tile_badge.dart';
|
|
|
|
import "package:photos/ui/map/map_marker.dart";
|
2023-06-07 11:08:25 +00:00
|
|
|
import "package:photos/ui/map/tile/layers.dart";
|
2023-06-26 06:54:04 +00:00
|
|
|
import "package:photos/utils/debouncer.dart";
|
2023-06-04 18:29:03 +00:00
|
|
|
|
|
|
|
class MapView extends StatefulWidget {
|
|
|
|
final List<ImageMarker> imageMarkers;
|
|
|
|
final Function updateVisibleImages;
|
|
|
|
final MapController controller;
|
2023-06-05 03:12:43 +00:00
|
|
|
final LatLng center;
|
2023-06-05 11:47:38 +00:00
|
|
|
final double minZoom;
|
|
|
|
final double maxZoom;
|
|
|
|
final double initialZoom;
|
|
|
|
final int debounceDuration;
|
2023-06-22 05:50:03 +00:00
|
|
|
final double bottomSheetDraggableAreaHeight;
|
2023-06-04 18:29:03 +00:00
|
|
|
|
|
|
|
const MapView({
|
|
|
|
Key? key,
|
|
|
|
required this.updateVisibleImages,
|
|
|
|
required this.imageMarkers,
|
|
|
|
required this.controller,
|
2023-06-05 03:12:43 +00:00
|
|
|
required this.center,
|
2023-06-05 11:47:38 +00:00
|
|
|
required this.minZoom,
|
|
|
|
required this.maxZoom,
|
|
|
|
required this.initialZoom,
|
|
|
|
required this.debounceDuration,
|
2023-06-22 05:50:03 +00:00
|
|
|
required this.bottomSheetDraggableAreaHeight,
|
2023-06-04 18:29:03 +00:00
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => _MapViewState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MapViewState extends State<MapView> {
|
2023-06-14 17:39:16 +00:00
|
|
|
late List<Marker> _markers;
|
2023-11-28 07:30:35 +00:00
|
|
|
final _debouncer = Debouncer(
|
|
|
|
const Duration(milliseconds: 300),
|
2023-11-30 13:59:27 +00:00
|
|
|
executionInterval: const Duration(milliseconds: 750),
|
2023-11-28 07:30:35 +00:00
|
|
|
);
|
2023-06-04 18:29:03 +00:00
|
|
|
|
2023-06-05 13:11:39 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2023-06-14 17:39:16 +00:00
|
|
|
_markers = _buildMakers();
|
2023-06-05 13:11:39 +00:00
|
|
|
}
|
|
|
|
|
2023-06-05 03:12:43 +00:00
|
|
|
@override
|
2023-06-05 11:47:38 +00:00
|
|
|
void dispose() {
|
|
|
|
super.dispose();
|
2023-06-04 18:29:03 +00:00
|
|
|
}
|
|
|
|
|
2023-06-14 18:27:34 +00:00
|
|
|
void onChange(LatLngBounds bounds) {
|
2023-06-26 06:54:04 +00:00
|
|
|
_debouncer.run(
|
|
|
|
() async {
|
|
|
|
widget.updateVisibleImages(bounds);
|
|
|
|
},
|
|
|
|
);
|
2023-06-14 18:27:34 +00:00
|
|
|
}
|
|
|
|
|
2023-06-04 18:29:03 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Stack(
|
|
|
|
children: [
|
|
|
|
FlutterMap(
|
|
|
|
mapController: widget.controller,
|
|
|
|
options: MapOptions(
|
2023-06-10 07:21:34 +00:00
|
|
|
center: widget.center,
|
2023-06-05 11:47:38 +00:00
|
|
|
minZoom: widget.minZoom,
|
|
|
|
maxZoom: widget.maxZoom,
|
2023-06-06 03:45:47 +00:00
|
|
|
enableMultiFingerGestureRace: true,
|
2023-06-10 07:21:34 +00:00
|
|
|
zoom: widget.initialZoom,
|
2023-06-20 16:21:14 +00:00
|
|
|
maxBounds: LatLngBounds(
|
2023-12-10 16:36:45 +00:00
|
|
|
const LatLng(-90, -180),
|
|
|
|
const LatLng(90, 180),
|
2023-06-20 16:21:14 +00:00
|
|
|
),
|
2023-06-05 11:47:38 +00:00
|
|
|
onPositionChanged: (position, hasGesture) {
|
|
|
|
if (position.bounds != null) {
|
2023-06-14 18:27:34 +00:00
|
|
|
onChange(position.bounds!);
|
2023-06-05 11:47:38 +00:00
|
|
|
}
|
|
|
|
},
|
2023-06-04 18:29:03 +00:00
|
|
|
),
|
2023-06-22 05:50:03 +00:00
|
|
|
nonRotatedChildren: [
|
2023-06-21 17:19:56 +00:00
|
|
|
Padding(
|
2023-06-22 05:50:03 +00:00
|
|
|
padding: EdgeInsets.only(
|
|
|
|
bottom: widget.bottomSheetDraggableAreaHeight,
|
|
|
|
),
|
|
|
|
child: const OSMFranceTileAttributes(),
|
2023-08-19 11:39:56 +00:00
|
|
|
),
|
2023-06-21 17:19:56 +00:00
|
|
|
],
|
2023-06-07 11:08:25 +00:00
|
|
|
children: [
|
|
|
|
const OSMFranceTileLayer(),
|
2023-06-06 09:45:18 +00:00
|
|
|
MarkerClusterLayerWidget(
|
|
|
|
options: MarkerClusterLayerOptions(
|
2023-09-26 16:12:26 +00:00
|
|
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
2023-06-06 09:45:18 +00:00
|
|
|
maxClusterRadius: 100,
|
2023-06-14 17:39:16 +00:00
|
|
|
showPolygon: false,
|
2023-06-06 09:45:18 +00:00
|
|
|
size: const Size(75, 75),
|
|
|
|
fitBoundsOptions: const FitBoundsOptions(
|
2023-06-30 08:36:55 +00:00
|
|
|
padding: EdgeInsets.all(80),
|
2023-06-06 09:45:18 +00:00
|
|
|
),
|
2023-06-14 17:39:16 +00:00
|
|
|
markers: _markers,
|
2023-06-14 18:27:34 +00:00
|
|
|
onClusterTap: (_) {
|
2023-06-28 11:39:35 +00:00
|
|
|
onChange(widget.controller.bounds!);
|
2023-06-14 18:27:34 +00:00
|
|
|
},
|
2023-06-14 17:39:16 +00:00
|
|
|
builder: (context, List<Marker> markers) {
|
2023-06-06 09:45:18 +00:00
|
|
|
final index = int.parse(
|
|
|
|
markers.first.key
|
|
|
|
.toString()
|
|
|
|
.replaceAll(RegExp(r'[^0-9]'), ''),
|
|
|
|
);
|
2023-06-14 17:39:16 +00:00
|
|
|
final String clusterKey =
|
|
|
|
'map-badge-$index-len-${markers.length}';
|
|
|
|
|
2023-06-06 09:45:18 +00:00
|
|
|
return Stack(
|
2023-06-14 17:39:16 +00:00
|
|
|
key: ValueKey(clusterKey),
|
2023-06-06 09:45:18 +00:00
|
|
|
children: [
|
|
|
|
MapGalleryTile(
|
|
|
|
key: Key(markers.first.key.toString()),
|
|
|
|
imageMarker: widget.imageMarkers[index],
|
|
|
|
),
|
2023-08-19 11:39:56 +00:00
|
|
|
MapGalleryTileBadge(size: markers.length),
|
2023-06-06 09:45:18 +00:00
|
|
|
],
|
|
|
|
);
|
|
|
|
},
|
2023-06-04 18:29:03 +00:00
|
|
|
),
|
2023-08-19 11:39:56 +00:00
|
|
|
),
|
2023-06-04 18:29:03 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
Positioned(
|
2023-06-10 09:52:54 +00:00
|
|
|
top: 4,
|
2023-06-04 18:29:03 +00:00
|
|
|
left: 10,
|
2023-06-10 09:52:54 +00:00
|
|
|
child: SafeArea(
|
|
|
|
child: MapButton(
|
|
|
|
icon: Icons.arrow_back,
|
|
|
|
onPressed: () {
|
|
|
|
Navigator.pop(context);
|
|
|
|
},
|
|
|
|
heroTag: 'back',
|
|
|
|
),
|
2023-06-04 18:29:03 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
Positioned(
|
2023-06-22 05:50:03 +00:00
|
|
|
bottom: widget.bottomSheetDraggableAreaHeight + 10,
|
2023-06-04 18:29:03 +00:00
|
|
|
right: 10,
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
MapButton(
|
|
|
|
icon: Icons.add,
|
|
|
|
onPressed: () {
|
|
|
|
widget.controller.move(
|
|
|
|
widget.controller.center,
|
|
|
|
widget.controller.zoom + 1,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
heroTag: 'zoom-in',
|
|
|
|
),
|
|
|
|
MapButton(
|
|
|
|
icon: Icons.remove,
|
|
|
|
onPressed: () {
|
|
|
|
widget.controller.move(
|
|
|
|
widget.controller.center,
|
|
|
|
widget.controller.zoom - 1,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
heroTag: 'zoom-out',
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
2023-06-14 17:39:16 +00:00
|
|
|
|
|
|
|
List<Marker> _buildMakers() {
|
|
|
|
return List<Marker>.generate(widget.imageMarkers.length, (index) {
|
|
|
|
final imageMarker = widget.imageMarkers[index];
|
|
|
|
return mapMarker(imageMarker, index.toString());
|
|
|
|
});
|
|
|
|
}
|
2023-06-04 18:29:03 +00:00
|
|
|
}
|