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'; import 'map_providers.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 markers, required List polylines, required bool isFollowMode, required MapConfig config, required ValueChanged? 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 markers; final List polylines; final bool isFollowMode; final ValueChanged? 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 _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: normalizeAngle(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), ); }, ); } }