195 lines
5.5 KiB
Dart
195 lines
5.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
|
|
import '../../core/theme/map_style.dart';
|
|
import 'map_provider_interface.dart';
|
|
|
|
class GoogleMapProvider implements AppMapProvider {
|
|
@override
|
|
AppMapType get type => AppMapType.googleMaps;
|
|
|
|
@override
|
|
String get name => 'Google Maps';
|
|
|
|
@override
|
|
Widget buildMapView({
|
|
required BuildContext context,
|
|
required AppLatLng center,
|
|
required double zoom,
|
|
double bearing = 0.0,
|
|
required List<AppMapMarker> markers,
|
|
required List<AppPolyline> polylines,
|
|
required bool isFollowMode,
|
|
required MapConfig config,
|
|
required ValueChanged<AppLatLng>? onPositionChanged,
|
|
required VoidCallback? onUserInteraction,
|
|
}) {
|
|
return _GoogleMapView(
|
|
center: center,
|
|
zoom: zoom,
|
|
bearing: bearing,
|
|
markers: markers,
|
|
polylines: polylines,
|
|
isFollowMode: isFollowMode,
|
|
onPositionChanged: onPositionChanged,
|
|
onUserInteraction: onUserInteraction,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _GoogleMapView extends StatefulWidget {
|
|
final AppLatLng center;
|
|
final double zoom;
|
|
final double bearing;
|
|
final List<AppMapMarker> markers;
|
|
final List<AppPolyline> polylines;
|
|
final bool isFollowMode;
|
|
final ValueChanged<AppLatLng>? onPositionChanged;
|
|
final VoidCallback? onUserInteraction;
|
|
|
|
const _GoogleMapView({
|
|
required this.center,
|
|
required this.zoom,
|
|
this.bearing = 0.0,
|
|
required this.markers,
|
|
required this.polylines,
|
|
required this.isFollowMode,
|
|
required this.onPositionChanged,
|
|
required this.onUserInteraction,
|
|
});
|
|
|
|
@override
|
|
State<_GoogleMapView> createState() => _GoogleMapViewState();
|
|
}
|
|
|
|
class _GoogleMapViewState extends State<_GoogleMapView> {
|
|
gmaps.GoogleMapController? _controller;
|
|
gmaps.LatLng? _lastFollowPos;
|
|
DateTime? _lastFollowTime;
|
|
bool _isProgrammaticMove = false;
|
|
|
|
static const int _followThrottleMs = 250;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_lastFollowPos = gmaps.LatLng(widget.center.latitude, widget.center.longitude);
|
|
}
|
|
|
|
Future<void> _moveCamera(gmaps.LatLng target, double zoom, double bearing) async {
|
|
final controller = _controller;
|
|
if (controller == null) return;
|
|
_lastFollowPos = target;
|
|
_lastFollowTime = DateTime.now();
|
|
_isProgrammaticMove = true;
|
|
try {
|
|
await controller.animateCamera(
|
|
gmaps.CameraUpdate.newCameraPosition(
|
|
gmaps.CameraPosition(
|
|
target: target,
|
|
zoom: zoom,
|
|
bearing: bearing,
|
|
),
|
|
),
|
|
);
|
|
} catch (_) {
|
|
} finally {
|
|
_isProgrammaticMove = false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant _GoogleMapView oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (_controller == null) return;
|
|
|
|
final target = gmaps.LatLng(widget.center.latitude, widget.center.longitude);
|
|
final zoomChanged = (oldWidget.zoom - widget.zoom).abs() > 0.01;
|
|
final bearingChanged = (oldWidget.bearing - widget.bearing).abs() > 0.1;
|
|
final justBecameFollowMode = !oldWidget.isFollowMode && widget.isFollowMode;
|
|
|
|
if (widget.isFollowMode) {
|
|
final now = DateTime.now();
|
|
final posDelta = _lastFollowPos == null
|
|
? 1.0
|
|
: (target.latitude - _lastFollowPos!.latitude).abs() +
|
|
(target.longitude - _lastFollowPos!.longitude).abs();
|
|
final hasMoved = posDelta > 0.00001;
|
|
final elapsedMs = _lastFollowTime == null
|
|
? _followThrottleMs + 1
|
|
: now.difference(_lastFollowTime!).inMilliseconds;
|
|
|
|
if (justBecameFollowMode ||
|
|
_lastFollowPos == null ||
|
|
zoomChanged ||
|
|
bearingChanged ||
|
|
(hasMoved && (elapsedMs >= _followThrottleMs || posDelta > 0.001))) {
|
|
_moveCamera(target, widget.zoom, widget.bearing);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final target = gmaps.LatLng(widget.center.latitude, widget.center.longitude);
|
|
|
|
final gMarkers = widget.markers.map((m) {
|
|
return gmaps.Marker(
|
|
markerId: gmaps.MarkerId(m.id),
|
|
position: gmaps.LatLng(m.position.latitude, m.position.longitude),
|
|
rotation: m.heading,
|
|
onTap: m.onTap,
|
|
);
|
|
}).toSet();
|
|
|
|
final gPolylines = widget.polylines.map((p) {
|
|
return gmaps.Polyline(
|
|
polylineId: gmaps.PolylineId(p.id),
|
|
points: p.points.map((pt) => gmaps.LatLng(pt.latitude, pt.longitude)).toList(),
|
|
color: p.color,
|
|
width: p.width.toInt(),
|
|
);
|
|
}).toSet();
|
|
|
|
final media = MediaQuery.of(context);
|
|
final sizeKey = '${media.orientation}_${media.size.width.toInt()}x${media.size.height.toInt()}';
|
|
|
|
return gmaps.GoogleMap(
|
|
key: ValueKey('google_map_$sizeKey'),
|
|
initialCameraPosition: gmaps.CameraPosition(
|
|
target: target,
|
|
zoom: widget.zoom,
|
|
bearing: widget.bearing,
|
|
),
|
|
markers: gMarkers,
|
|
polylines: gPolylines,
|
|
style: darkMapStyle,
|
|
myLocationEnabled: false,
|
|
zoomControlsEnabled: false,
|
|
compassEnabled: false,
|
|
onMapCreated: (controller) {
|
|
_controller = controller;
|
|
if (widget.isFollowMode) {
|
|
_moveCamera(target, widget.zoom, widget.bearing);
|
|
}
|
|
},
|
|
onCameraMoveStarted: () {
|
|
if (!_isProgrammaticMove) {
|
|
widget.onUserInteraction?.call();
|
|
}
|
|
},
|
|
onCameraMove: (position) {
|
|
widget.onPositionChanged?.call(
|
|
AppLatLng(position.target.latitude, position.target.longitude),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|