MotorSignal-App/lib/services/map/google_map_provider.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),
);
},
);
}
}